SMP all works
This commit is contained in:
4
semester-4/СмП/lb-2/README.md
Normal file
4
semester-4/СмП/lb-2/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Сокорчук І. П.
|
||||
>
|
||||
> Оцінка: 100
|
469
semester-4/СмП/lb-2/src/pzpi-23-2-sytnyk-yehor-task3.php
Executable file
469
semester-4/СмП/lb-2/src/pzpi-23-2-sytnyk-yehor-task3.php
Executable file
@ -0,0 +1,469 @@
|
||||
#!/usr/bin/php
|
||||
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
class AppException extends Exception {}
|
||||
|
||||
enum State
|
||||
{
|
||||
case Hello;
|
||||
case Menu;
|
||||
case Items;
|
||||
case Checkout;
|
||||
case Settins;
|
||||
case Exit;
|
||||
}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
name TEXT,
|
||||
age TEXT
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising settings table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 12);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 9);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 21);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 25);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 19);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 18);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES item(id)
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user information in the database
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $age
|
||||
* @return void
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function update_user($name, $age): void
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;");
|
||||
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error updating user info.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database without price info.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_no_price(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT name, count FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id, name, price, count, price*count as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart
|
||||
(id, count)
|
||||
VALUES
|
||||
(:id, :count)
|
||||
ON CONFLICT(id)
|
||||
DO UPDATE SET
|
||||
count = :count
|
||||
WHERE id = :id;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding item to the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removimg item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class App
|
||||
{
|
||||
private $db;
|
||||
private $state;
|
||||
|
||||
private $menu_ops = <<<'END'
|
||||
1 Вибрати товари
|
||||
2 Отримати підсумковий рахунок
|
||||
3 Налаштувати свій профіль
|
||||
0 Вийти з програми
|
||||
END;
|
||||
private $hello = <<<'END'
|
||||
################################
|
||||
# ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" #
|
||||
################################
|
||||
END;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $db_path
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->db = new DB($db_path);
|
||||
$this->state = State::Hello;
|
||||
} catch (DbException $e) {
|
||||
throw new AppException("Error initializing app.\nCaused by: " . $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function poll(): void
|
||||
{
|
||||
while ($this->state != State::Exit) {
|
||||
switch ($this->state) {
|
||||
case State::Hello:
|
||||
$this->hello();
|
||||
break;
|
||||
case State::Menu:
|
||||
$this->menu();
|
||||
break;
|
||||
case State::Items:
|
||||
$this->items();
|
||||
break;
|
||||
case State::Checkout:
|
||||
$this->checkout();
|
||||
break;
|
||||
case State::Settins:
|
||||
$this->settings();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function menu(): void
|
||||
{
|
||||
echo "\n";
|
||||
echo "$this->menu_ops\n";
|
||||
|
||||
$op = readline('Введіть команду: ');
|
||||
switch ($op) {
|
||||
case '1':
|
||||
$this->state = State::Items;
|
||||
break;
|
||||
case '2':
|
||||
$this->state = State::Checkout;
|
||||
break;
|
||||
case '3':
|
||||
$this->state = State::Settins;
|
||||
break;
|
||||
case '0':
|
||||
$this->state = State::Exit;
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "ПОМИЛКА! Введіть правильну команду\n";
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
private function hello(): void
|
||||
{
|
||||
echo "$this->hello\n";
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function items(): void
|
||||
{
|
||||
$items = $this->db->get_items();
|
||||
array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]);
|
||||
array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]);
|
||||
array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]);
|
||||
$columns = $this->count_columns($items);
|
||||
|
||||
while (true) {
|
||||
$this->print_lits($items, $columns);
|
||||
|
||||
$id = readline("Виберіть товар: ");
|
||||
|
||||
if ($id == '0') {
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$selected = null;
|
||||
foreach ($items as $item) {
|
||||
if ($item['id'] === (int)$id) $selected = $item;
|
||||
}
|
||||
|
||||
if ($selected == null) {
|
||||
echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "Вибрано: {$selected['name']}\n";
|
||||
|
||||
$count = (int)readline("Введіть кількість, штук: ");
|
||||
|
||||
if ($count > 100) {
|
||||
echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count < 0) {
|
||||
echo "ПОМИЛКА! Кількість не може бути від'ємною\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count == 0) {
|
||||
echo "ВИДАЛЯЮ З КОШИКА\n";
|
||||
$this->db->remove_from_cart($id);
|
||||
} else {
|
||||
$this->db->add_to_cart($id, $count);
|
||||
}
|
||||
|
||||
$cart = $this->db->get_cart_no_price();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
} else {
|
||||
echo "\nУ КОШИКУ:\n";
|
||||
array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function checkout(): void
|
||||
{
|
||||
$cart = $this->db->get_cart();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
$this->state = State::Menu;
|
||||
return;
|
||||
} else {
|
||||
echo "У КОШИКУ:\n";
|
||||
array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
}
|
||||
|
||||
$total_price = array_reduce($cart, function ($carry, $item) {
|
||||
return $carry + (int)$item['total_price'];
|
||||
}, 0);
|
||||
echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n";
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function settings(): void
|
||||
{
|
||||
while (true) {
|
||||
$name = readline("Ваше ім'я: ");
|
||||
if ($name !== "" && preg_match("/[a-zA-Z]+/", $name))
|
||||
break;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$age = readline("Ваш вік: ");
|
||||
|
||||
if (!filter_var($age, FILTER_VALIDATE_INT)) {
|
||||
echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($age < 7 || $age > 150) {
|
||||
echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$this->db->update_user($name, $age);
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @return array<int>
|
||||
*/
|
||||
private function count_columns($items): array
|
||||
{
|
||||
$columns = [];
|
||||
foreach ($items as $item) {
|
||||
foreach ($item as $field => $value) {
|
||||
if (!key_exists($field, $columns))
|
||||
$columns[$field] = mb_strlen($value);
|
||||
else
|
||||
$columns[$field] = max(mb_strlen($value), $columns[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
/**
|
||||
* @param array $element
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function pad_row($element, $columns): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($element as $field => $value)
|
||||
$result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT);
|
||||
|
||||
return implode(" ", $result);
|
||||
}
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function print_lits($items, $columns): void
|
||||
{
|
||||
foreach ($items as $item)
|
||||
echo $this->pad_row($item, $columns) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$app = new App("db.sqlite");
|
||||
} catch (AppException $e) {
|
||||
echo $e;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$app->poll();
|
BIN
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.pdf
Normal file
BIN
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.pdf
Normal file
Binary file not shown.
681
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.txt
Normal file
681
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.txt
Normal file
@ -0,0 +1,681 @@
|
||||
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
|
||||
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
|
||||
|
||||
|
||||
|
||||
Кафедра Програмної інженерії
|
||||
|
||||
|
||||
|
||||
|
||||
Звіт
|
||||
з лабораторної роботи №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 <?php
|
||||
4
|
||||
5 class DbException extends Exception {}
|
||||
6 class AppException extends Exception {}
|
||||
7
|
||||
8 enum State
|
||||
9 {
|
||||
10 case Hello;
|
||||
11 case Menu;
|
||||
12 case Items;
|
||||
13 case Checkout;
|
||||
14 case Settins;
|
||||
15 case Exit;
|
||||
16 }
|
||||
17
|
||||
18 class DB
|
||||
19 {
|
||||
20 private $pdo;
|
||||
21
|
||||
22 /**
|
||||
23 * Initializes database
|
||||
24 *
|
||||
25 * @param string $db_path
|
||||
26 * @throws DbException If there's a database error.
|
||||
27 */
|
||||
28 public function __construct($db_path)
|
||||
29 {
|
||||
30 try {
|
||||
31 $this->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<array> $items
|
||||
424 * @return array<int>
|
||||
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<int> $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<array> $items
|
||||
454 * @param array<int> $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();
|
||||
|
644
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.typ
Normal file
644
semester-4/СмП/lb-2/Лр_2_Ситник_ПЗПІ_23_2.typ
Normal file
@ -0,0 +1,644 @@
|
||||
#import "@local/nure:0.1.0": *
|
||||
|
||||
#show: pz-lb.with(
|
||||
title: [Продовольчий магазин "Весна"],
|
||||
subject: "СМП",
|
||||
doctype: "ЛБ",
|
||||
worknumber: 2,
|
||||
edu_program: "ПЗПІ",
|
||||
university: "ХНУРЕ",
|
||||
mentors: (
|
||||
(
|
||||
name: "Сокорчук І. П.",
|
||||
degree: "Старший викладач кафедри ПІ",
|
||||
gender: "m",
|
||||
),
|
||||
),
|
||||
authors: (
|
||||
(
|
||||
name: "Ситник Є. С.",
|
||||
course: 2,
|
||||
edu: "ПЗПІ",
|
||||
gender: "m",
|
||||
group: "23-2",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
#v(-spacing)
|
||||
== Історія змін
|
||||
#figure(
|
||||
table(
|
||||
align: left,
|
||||
columns: (auto, 1fr, 1fr, 3fr),
|
||||
table.header([№], [Дата], [Версія звіту], [Опис змін та виправлень]),
|
||||
[1], [03.03.2025], [0.1], [Створено звіт],
|
||||
),
|
||||
)
|
||||
|
||||
== Мета роботи
|
||||
Лабораторна робота полягає у розробці консольного застосунку "Продовольчий магазин "Весна"" засобами мови програмування "PHP".
|
||||
|
||||
== Хід роботи
|
||||
#v(-spacing)
|
||||
=== Архітектура програми
|
||||
|
||||
Для реалізації консольного застосунку було обрано об'єктно-орієнтований підхід з використанням наступних компонентів:
|
||||
|
||||
==== Класи та їх призначення:
|
||||
|
||||
- *DB* -- клас для роботи з базою даних "SQLite", що забезпечує збереження даних про товари, кошик користувача та налаштування профілю;
|
||||
- *App* -- основний клас застосунку, що реалізує логіку роботи програми та взаємодію з користувачем;
|
||||
- *DbException* та *AppException* -- класи винятків для обробки помилок.
|
||||
|
||||
==== Enum State:
|
||||
Для управління станами програми використовується перелічення "State" з наступними значеннями:
|
||||
- *Hello* -- привітальний екран;
|
||||
- *Menu* -- головне меню;
|
||||
- *Items* -- вибір товарів;
|
||||
- *Checkout* -- формування рахунку;
|
||||
- *Settings* -- налаштування профілю;
|
||||
- *Exit* -- вихід з програми.
|
||||
|
||||
=== Структура бази даних
|
||||
|
||||
Програма використовує базу даних SQLite з трьома таблицями:
|
||||
|
||||
- *settings* -- зберігає інформацію про користувача (ім'я та вік);
|
||||
- *items* -- містить каталог товарів з їх назвами та цінами;
|
||||
- *cart* -- зберігає товари, додані до кошика, з їх кількістю.
|
||||
|
||||
=== Основний функціонал
|
||||
|
||||
==== Головне меню:
|
||||
Програма відображає заголовок магазину та пропонує користувачу чотири основні опції:
|
||||
- вибір товарів для покупки;
|
||||
- перегляд підсумкового рахунку;
|
||||
- налаштування профілю користувача;
|
||||
- вихід з програми.
|
||||
|
||||
==== Вибір товарів:
|
||||
При виборі першого пункту меню користувач потрапляє в режим покупки товарів, де:
|
||||
- відображається список доступних товарів з цінами;
|
||||
- можна вибрати товар за номером та вказати кількість;
|
||||
- товари додаються до кошика;
|
||||
- при введенні кількості "0" товар видаляється з кошика;
|
||||
- реалізована валідація введених даних.
|
||||
|
||||
==== Підсумковий рахунок:
|
||||
Другий пункт меню формує детальний рахунок з інформацією про:
|
||||
- номер, назву та ціну кожного товару;
|
||||
- кількість товару в кошику;
|
||||
- загальну вартість кожного товару;
|
||||
- підсумкову суму до сплати.
|
||||
|
||||
==== Налаштування профілю:
|
||||
Третій пункт дозволяє користувачу вказати своє ім'я та вік з валідацією:
|
||||
- ім'я повинно містити хоча б одну літеру;
|
||||
- вік повинен бути в діапазоні від 7 до 150 років.
|
||||
|
||||
=== Технічні деталі
|
||||
|
||||
Програма написана з дотриманням сучасних стандартів "PHP":
|
||||
- використання строгої типізації;
|
||||
- документування методів за допомогою "PHPDoc";
|
||||
- дотримання принципів "SOLID";
|
||||
- розділення логіки на окремі методи для кращої читабельності.
|
||||
|
||||
== Висновки
|
||||
|
||||
Під час даної лабораторної роботи я вивчив основні принципи роботи з "PHP" для написання консольних програм. Зокрема, було освоєно:
|
||||
|
||||
- створення об'єктно-орієнтованих консольних застосунків на "PHP";
|
||||
- роботу з базою даних "SQLite" через "PDO";
|
||||
- обробку користувацького вводу та валідацію даних;
|
||||
- використання перелічень ("enum") для управління станами програми;
|
||||
- реалізацію системи обробки винятків;
|
||||
- форматування виводу в консолі для створення зручного інтерфейсу.
|
||||
|
||||
Програмне забезпечення, реалізоване протягом даної лабораторної роботи, повністю відповідає поставленим вимогам та забезпечує всі необхідні функції для роботи з продовольчим магазином. Застосунок має зручний інтерфейс, надійну систему валідації даних та ефективно працює з базою даних для збереження інформації про товари та користувача.
|
||||
|
||||
#set raw(tab-size: 8)
|
||||
#show raw.where(block: true): code => {
|
||||
// set text(11pt, font: "Caladea", top-edge: 1em, bottom-edge: 0em)
|
||||
set text(11pt, top-edge: 1em, bottom-edge: 0em)
|
||||
set par(leading: 0.17em)
|
||||
|
||||
grid(
|
||||
columns: (auto, auto),
|
||||
column-gutter: 1em,
|
||||
row-gutter: 0.17em,
|
||||
align: (right, raw.align),
|
||||
..for line in code.lines {
|
||||
(
|
||||
text(fill: gray)[#line.number],
|
||||
{
|
||||
set text(weight: "semibold")
|
||||
line.body
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
#show: appendices_style
|
||||
= Відеозапис
|
||||
Відеозапис презентації результатів лабораторної роботи: https://youtu.be/CMGeL1OdyvI
|
||||
|
||||
*Хронологічний опис відеозапису:*
|
||||
|
||||
00:00 -- Вступ та опис завдання
|
||||
|
||||
00:19 -- Архітектура програми
|
||||
|
||||
00:39 -- Робота з базою даних
|
||||
|
||||
01:43 -- Реалізація станів програми
|
||||
|
||||
02:17 -- Формування списку товарів
|
||||
|
||||
03:13 -- Додавання товарів до кошика
|
||||
|
||||
04:15 -- Робота з кошиком
|
||||
|
||||
05:10 -- Налаштування профілю користувача
|
||||
|
||||
05:38 -- Демонстрація роботи програми
|
||||
|
||||
= Програмний код
|
||||
== Скрипт з реалізацією програми
|
||||
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
|
||||
|
||||
#v(spacing)
|
||||
|
||||
```
|
||||
#!/usr/bin/php
|
||||
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
class AppException extends Exception {}
|
||||
|
||||
enum State
|
||||
{
|
||||
case Hello;
|
||||
case Menu;
|
||||
case Items;
|
||||
case Checkout;
|
||||
case Settins;
|
||||
case Exit;
|
||||
}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
name TEXT,
|
||||
age TEXT
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising settings table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 12);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 9);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 21);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 25);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 19);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 18);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES item(id)
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user information in the database
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $age
|
||||
* @return void
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function update_user($name, $age): void
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;");
|
||||
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error updating user info.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database without price info.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_no_price(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT name, count FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id, name, price, count, price*count as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart
|
||||
(id, count)
|
||||
VALUES
|
||||
(:id, :count)
|
||||
ON CONFLICT(id)
|
||||
DO UPDATE SET
|
||||
count = :count
|
||||
WHERE id = :id;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding item to the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removimg item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class App
|
||||
{
|
||||
private $db;
|
||||
private $state;
|
||||
|
||||
private $menu_ops = <<<'END'
|
||||
1 Вибрати товари
|
||||
2 Отримати підсумковий рахунок
|
||||
3 Налаштувати свій профіль
|
||||
0 Вийти з програми
|
||||
END;
|
||||
private $hello = <<<'END'
|
||||
################################
|
||||
# ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" #
|
||||
################################
|
||||
END;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $db_path
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->db = new DB($db_path);
|
||||
$this->state = State::Hello;
|
||||
} catch (DbException $e) {
|
||||
throw new AppException("Error initializing app.\nCaused by: " . $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function poll(): void
|
||||
{
|
||||
while ($this->state != State::Exit) {
|
||||
switch ($this->state) {
|
||||
case State::Hello:
|
||||
$this->hello();
|
||||
break;
|
||||
case State::Menu:
|
||||
$this->menu();
|
||||
break;
|
||||
case State::Items:
|
||||
$this->items();
|
||||
break;
|
||||
case State::Checkout:
|
||||
$this->checkout();
|
||||
break;
|
||||
case State::Settins:
|
||||
$this->settings();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function menu(): void
|
||||
{
|
||||
echo "\n";
|
||||
echo "$this->menu_ops\n";
|
||||
|
||||
$op = readline('Введіть команду: ');
|
||||
switch ($op) {
|
||||
case '1':
|
||||
$this->state = State::Items;
|
||||
break;
|
||||
case '2':
|
||||
$this->state = State::Checkout;
|
||||
break;
|
||||
case '3':
|
||||
$this->state = State::Settins;
|
||||
break;
|
||||
case '0':
|
||||
$this->state = State::Exit;
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "ПОМИЛКА! Введіть правильну команду\n";
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
private function hello(): void
|
||||
{
|
||||
echo "$this->hello\n";
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function items(): void
|
||||
{
|
||||
$items = $this->db->get_items();
|
||||
array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]);
|
||||
array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]);
|
||||
array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]);
|
||||
$columns = $this->count_columns($items);
|
||||
|
||||
while (true) {
|
||||
$this->print_lits($items, $columns);
|
||||
|
||||
$id = readline("Виберіть товар: ");
|
||||
|
||||
if ($id == '0') {
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$selected = null;
|
||||
foreach ($items as $item) {
|
||||
if ($item['id'] === (int)$id)
|
||||
$selected = $item;
|
||||
}
|
||||
|
||||
if ($selected == null) {
|
||||
echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "Вибрано: {$selected['name']}\n";
|
||||
|
||||
$count = (int)readline("Введіть кількість, штук: ");
|
||||
|
||||
if ($count > 100) {
|
||||
echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count < 0) {
|
||||
echo "ПОМИЛКА! Кількість не може бути від'ємною\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count == 0) {
|
||||
echo "ВИДАЛЯЮ З КОШИКА\n";
|
||||
$this->db->remove_from_cart($id);
|
||||
} else {
|
||||
$this->db->add_to_cart($id, $count);
|
||||
}
|
||||
|
||||
$cart = $this->db->get_cart_no_price();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
} else {
|
||||
echo "\nУ КОШИКУ:\n";
|
||||
array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function checkout(): void
|
||||
{
|
||||
$cart = $this->db->get_cart();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
$this->state = State::Menu;
|
||||
return;
|
||||
} else {
|
||||
echo "У КОШИКУ:\n";
|
||||
array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
}
|
||||
|
||||
$total_price = array_reduce($cart, function ($carry, $item) {
|
||||
return $carry + (int)$item['total_price'];
|
||||
}, 0);
|
||||
echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n";
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function settings(): void
|
||||
{
|
||||
while (true) {
|
||||
$name = readline("Ваше ім'я: ");
|
||||
if ($name !== "" && preg_match("/[a-zA-Z]+/", $name))
|
||||
break;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$age = readline("Ваш вік: ");
|
||||
|
||||
if (!filter_var($age, FILTER_VALIDATE_INT)) {
|
||||
echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($age < 7 || $age > 150) {
|
||||
echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$this->db->update_user($name, $age);
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @return array<int>
|
||||
*/
|
||||
private function count_columns($items): array
|
||||
{
|
||||
$columns = [];
|
||||
foreach ($items as $item) {
|
||||
foreach ($item as $field => $value) {
|
||||
if (!key_exists($field, $columns))
|
||||
$columns[$field] = mb_strlen($value);
|
||||
else
|
||||
$columns[$field] = max(mb_strlen($value), $columns[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
/**
|
||||
* @param array $element
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function pad_row($element, $columns): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($element as $field => $value)
|
||||
$result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT);
|
||||
|
||||
return implode(" ", $result);
|
||||
}
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function print_lits($items, $columns): void
|
||||
{
|
||||
foreach ($items as $item)
|
||||
echo $this->pad_row($item, $columns) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$app = new App("db.sqlite");
|
||||
} catch (AppException $e) {
|
||||
echo $e;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$app->poll();
|
||||
```
|
4
semester-4/СмП/lb-3/README.md
Normal file
4
semester-4/СмП/lb-3/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Сокорчук І. П.
|
||||
>
|
||||
> Оцінка: 80
|
469
semester-4/СмП/lb-3/src/pzpi-23-2-sytnyk-yehor-task3.php
Executable file
469
semester-4/СмП/lb-3/src/pzpi-23-2-sytnyk-yehor-task3.php
Executable file
@ -0,0 +1,469 @@
|
||||
#!/usr/bin/php
|
||||
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
class AppException extends Exception {}
|
||||
|
||||
enum State
|
||||
{
|
||||
case Hello;
|
||||
case Menu;
|
||||
case Items;
|
||||
case Checkout;
|
||||
case Settins;
|
||||
case Exit;
|
||||
}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
name TEXT,
|
||||
age TEXT
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising settings table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 12);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 9);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 21);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 25);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 19);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 18);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 14);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES item(id)
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user information in the database
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $age
|
||||
* @return void
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function update_user($name, $age): void
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("UPDATE settings SET name = :name, age = :age;");
|
||||
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error updating user info.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database without price info.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_no_price(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT name, count FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id, name, price, count, price*count as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error inserting init data in the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart
|
||||
(id, count)
|
||||
VALUES
|
||||
(:id, :count)
|
||||
ON CONFLICT(id)
|
||||
DO UPDATE SET
|
||||
count = :count
|
||||
WHERE id = :id;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding item to the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removimg item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class App
|
||||
{
|
||||
private $db;
|
||||
private $state;
|
||||
|
||||
private $menu_ops = <<<'END'
|
||||
1 Вибрати товари
|
||||
2 Отримати підсумковий рахунок
|
||||
3 Налаштувати свій профіль
|
||||
0 Вийти з програми
|
||||
END;
|
||||
private $hello = <<<'END'
|
||||
################################
|
||||
# ПРОДОВОЛЬЧИЙ МАГАЗИН "ВЕСНА" #
|
||||
################################
|
||||
END;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $db_path
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->db = new DB($db_path);
|
||||
$this->state = State::Hello;
|
||||
} catch (DbException $e) {
|
||||
throw new AppException("Error initializing app.\nCaused by: " . $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function poll(): void
|
||||
{
|
||||
while ($this->state != State::Exit) {
|
||||
switch ($this->state) {
|
||||
case State::Hello:
|
||||
$this->hello();
|
||||
break;
|
||||
case State::Menu:
|
||||
$this->menu();
|
||||
break;
|
||||
case State::Items:
|
||||
$this->items();
|
||||
break;
|
||||
case State::Checkout:
|
||||
$this->checkout();
|
||||
break;
|
||||
case State::Settins:
|
||||
$this->settings();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function menu(): void
|
||||
{
|
||||
echo "\n";
|
||||
echo "$this->menu_ops\n";
|
||||
|
||||
$op = readline('Введіть команду: ');
|
||||
switch ($op) {
|
||||
case '1':
|
||||
$this->state = State::Items;
|
||||
break;
|
||||
case '2':
|
||||
$this->state = State::Checkout;
|
||||
break;
|
||||
case '3':
|
||||
$this->state = State::Settins;
|
||||
break;
|
||||
case '0':
|
||||
$this->state = State::Exit;
|
||||
break;
|
||||
|
||||
default:
|
||||
echo "ПОМИЛКА! Введіть правильну команду\n";
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
private function hello(): void
|
||||
{
|
||||
echo "$this->hello\n";
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function items(): void
|
||||
{
|
||||
$items = $this->db->get_items();
|
||||
array_unshift($items, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА"]);
|
||||
array_push($items, ['id' => " ", 'name' => "-----------", 'price' => ""]);
|
||||
array_push($items, ['id' => "0", 'name' => "ПОВЕРНУТИСЯ", 'price' => ""]);
|
||||
$columns = $this->count_columns($items);
|
||||
|
||||
while (true) {
|
||||
$this->print_lits($items, $columns);
|
||||
|
||||
$id = readline("Виберіть товар: ");
|
||||
|
||||
if ($id == '0') {
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$selected = null;
|
||||
foreach ($items as $item) {
|
||||
if ($item['id'] === (int)$id) $selected = $item;
|
||||
}
|
||||
|
||||
if ($selected == null) {
|
||||
echo "ПОМИЛКА! ВКАЗАНО НЕПРАВИЛЬНИЙ НОМЕР ТОВАРУ\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "Вибрано: {$selected['name']}\n";
|
||||
|
||||
$count = (int)readline("Введіть кількість, штук: ");
|
||||
|
||||
if ($count > 100) {
|
||||
echo "ПОМИЛКА! Не можна додати більше 100 одиниць товару в кошик\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count < 0) {
|
||||
echo "ПОМИЛКА! Кількість не може бути від'ємною\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($count == 0) {
|
||||
echo "ВИДАЛЯЮ З КОШИКА\n";
|
||||
$this->db->remove_from_cart($id);
|
||||
} else {
|
||||
$this->db->add_to_cart($id, $count);
|
||||
}
|
||||
|
||||
$cart = $this->db->get_cart_no_price();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
} else {
|
||||
echo "\nУ КОШИКУ:\n";
|
||||
array_unshift($cart, ['name' => "НАЗВА", 'count' => "КІЛЬКІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function checkout(): void
|
||||
{
|
||||
$cart = $this->db->get_cart();
|
||||
if (count($cart) == 0) {
|
||||
echo "КОШИК ПОРОЖНІЙ\n";
|
||||
$this->state = State::Menu;
|
||||
return;
|
||||
} else {
|
||||
echo "У КОШИКУ:\n";
|
||||
array_unshift($cart, ['id' => "№", 'name' => "НАЗВА", 'price' => "ЦІНА", 'count' => "КІЛЬКІСТЬ", 'total_price' => "ВАРТІСТЬ"]);
|
||||
$cart_columns = $this->count_columns($cart);
|
||||
$this->print_lits($cart, $cart_columns);
|
||||
}
|
||||
|
||||
$total_price = array_reduce($cart, function ($carry, $item) {
|
||||
return $carry + (int)$item['total_price'];
|
||||
}, 0);
|
||||
echo "РАЗОМ ДО СПЛАТИ: {$total_price}\n";
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
private function settings(): void
|
||||
{
|
||||
while (true) {
|
||||
$name = readline("Ваше ім'я: ");
|
||||
if ($name !== "" && preg_match("/[a-zA-Z]+/", $name))
|
||||
break;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$age = readline("Ваш вік: ");
|
||||
|
||||
if (!filter_var($age, FILTER_VALIDATE_INT)) {
|
||||
echo "ПОМИЛКА! Вік користувача потрібно вказати числом\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($age < 7 || $age > 150) {
|
||||
echo "ПОМИЛКА! Користувач повинен мати вік від 7 та до 150 років\n\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
$this->db->update_user($name, $age);
|
||||
|
||||
$this->state = State::Menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @return array<int>
|
||||
*/
|
||||
private function count_columns($items): array
|
||||
{
|
||||
$columns = [];
|
||||
foreach ($items as $item) {
|
||||
foreach ($item as $field => $value) {
|
||||
if (!key_exists($field, $columns))
|
||||
$columns[$field] = mb_strlen($value);
|
||||
else
|
||||
$columns[$field] = max(mb_strlen($value), $columns[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
/**
|
||||
* @param array $element
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function pad_row($element, $columns): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($element as $field => $value)
|
||||
$result[] = mb_str_pad($value, $columns[$field], ' ', STR_PAD_RIGHT);
|
||||
|
||||
return implode(" ", $result);
|
||||
}
|
||||
/**
|
||||
* @param array<array> $items
|
||||
* @param array<int> $columns
|
||||
*/
|
||||
private function print_lits($items, $columns): void
|
||||
{
|
||||
foreach ($items as $item)
|
||||
echo $this->pad_row($item, $columns) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$app = new App("db.sqlite");
|
||||
} catch (AppException $e) {
|
||||
echo $e;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$app->poll();
|
BIN
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.pdf
Normal file
BIN
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.pdf
Normal file
Binary file not shown.
785
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.txt
Normal file
785
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.txt
Normal file
@ -0,0 +1,785 @@
|
||||
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
|
||||
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
|
||||
|
||||
|
||||
|
||||
Кафедра Програмної інженерії
|
||||
|
||||
|
||||
|
||||
|
||||
Звіт
|
||||
з лабораторної роботи №3
|
||||
з дисципліни: «Скриптові мови програмування»
|
||||
з теми: « Створення WEB-застосунків за допомогою PHP «
|
||||
|
||||
|
||||
|
||||
|
||||
Виконав: Перевірив:
|
||||
ст. гр. ПЗПІ-23-2 Старший викладач кафедри ПІ
|
||||
Ситник Є. С. Сокорчук І. П.
|
||||
|
||||
|
||||
|
||||
|
||||
Харків – 2025
|
||||
2
|
||||
3 СТВОРЕННЯ WEB-ЗАСТОСУНКІВ ЗА ДОПОМОГОЮ PHP
|
||||
3.1 Історія змін
|
||||
|
||||
№ Дата Версія звіту Опис змін та виправлень
|
||||
1 03.06.2025 0.1 Створено звіт
|
||||
|
||||
3.2 Мета роботи
|
||||
|
||||
Лабораторна робота полягає у розробці веб-застосунку «Продовольчий
|
||||
магазин Весна» засобами мови програмування PHP з використанням HTML, CSS та
|
||||
JavaScript для створення інтерактивного інтерфейсу користувача.
|
||||
|
||||
3.3 Хід роботи
|
||||
3.3.1 Архітектура веб-застосунку
|
||||
|
||||
Для реалізації веб-застосунку було обрано модульний підхід з розділенням
|
||||
логіки на окремі файли:
|
||||
|
||||
3.3.1.1 Структура файлів:
|
||||
|
||||
– index.php – головна сторінка застосунку з логотипом магазину;
|
||||
– items.php – сторінка каталогу товарів з можливістю додавання до кошика;
|
||||
– cart.php – сторінка кошика з переглядом обраних товарів та управлінням;
|
||||
– handle_cart.php – обробник AJAX-запитів для роботи з кошиком;
|
||||
– DB.php – клас для роботи з базою даних SQLite;
|
||||
– style.css – стилі для оформлення веб-сторінок.
|
||||
|
||||
3.3.1.2 Основні компоненти:
|
||||
|
||||
– Клас DB – забезпечує взаємодію з базою даних SQLite, включаючи
|
||||
методи для роботи з товарами, кошиком та налаштуваннями користувача;
|
||||
– Клас DbException – спеціалізований виняток для обробки помилок бази
|
||||
даних;
|
||||
– Сесійна система – для збереження стану користувача між сторінками.
|
||||
3
|
||||
3.3.2 Структура бази даних
|
||||
|
||||
Веб-застосунок використовує базу даних SQLite з трьома основними
|
||||
таблицями:
|
||||
|
||||
3.3.2.1 Таблиця settings:
|
||||
|
||||
– name (TEXT) – ім’я користувача;
|
||||
– age (TEXT) – вік користувача.
|
||||
|
||||
3.3.2.2 Таблиця items:
|
||||
|
||||
– id (INTEGER PRIMARY KEY) – унікальний ідентифікатор товару;
|
||||
– name (TEXT NOT NULL) – назва товару;
|
||||
– price (REAL NOT NULL) – ціна товару.
|
||||
|
||||
3.3.2.3 Таблиця cart:
|
||||
|
||||
– id (INTEGER NOT NULL UNIQUE) – ідентифікатор товару;
|
||||
– count (INTEGER NOT NULL) – кількість товару в кошику;
|
||||
– FOREIGN KEY – зв’язок з таблицею items.
|
||||
|
||||
3.3.3 Функціональні можливості
|
||||
|
||||
3.3.3.1 Головна сторінка (index.php):
|
||||
|
||||
– Відображення логотипу магазину;
|
||||
– Навігаційне меню з кількістю товарів у кошику;
|
||||
– Привітання користувача за ім’ям;
|
||||
– Підвал з інформацією про магазин.
|
||||
|
||||
3.3.3.2 Каталог товарів (items.php):
|
||||
|
||||
– Відображення списку доступних товарів з цінами;
|
||||
– Форма для вибору кількості товару;
|
||||
– Можливість додавання товарів до кошика;
|
||||
– Валідація кількості товару (від 0 до 100 одиниць);
|
||||
4
|
||||
– Автоматичне оновлення лічильника кошика.
|
||||
|
||||
3.3.3.3 Кошик покупок (cart.php):
|
||||
|
||||
– Перегляд обраних товарів з детальною інформацією;
|
||||
– Відображення кількості, ціни за одиницю та загальної вартості;
|
||||
– Можливість видалення окремих товарів з кошика;
|
||||
– Функція повного очищення кошика;
|
||||
– Підрахунок загальної суми покупки;
|
||||
– Кнопка «Сплатити» для завершення покупки.
|
||||
|
||||
3.3.3.4 Обробка запитів (handle_cart.php):
|
||||
|
||||
– Обробка POST-запитів для додавання товарів до кошика;
|
||||
– Обробка DELETE-запитів для видалення товарів;
|
||||
– Підтримка AJAX для асинхронного оновлення кошика;
|
||||
– Валідація вхідних даних та обробка помилок.
|
||||
|
||||
3.3.4 Технічні особливості
|
||||
|
||||
3.3.4.1 Безпека:
|
||||
|
||||
– Використання підготовлених запитів (prepared statements) для запобігання
|
||||
SQL-ін’єкціям;
|
||||
– Функція «htmlspecialchars()» для запобігання XSS-атакам;
|
||||
– Валідація та фільтрація користувацьких даних через «filter_input()»;
|
||||
– Обробка винятків для коректної роботи з помилками.
|
||||
|
||||
3.3.4.2 Користувацький інтерфейс:
|
||||
|
||||
– Адаптивний дизайн з використанням CSS;
|
||||
– Інтуїтивна навігація між сторінками;
|
||||
– Зручні форми для взаємодії з користувачем.
|
||||
5
|
||||
3.3.4.3 База даних:
|
||||
|
||||
– Автоматичне створення таблиць при першому запуску;
|
||||
– Заповнення початковими даними;
|
||||
|
||||
3.3.5 Методи класу DB
|
||||
|
||||
Клас DB містить наступні основні методи:
|
||||
– retrieve_user() – отримання інформації про користувача;
|
||||
– update_user() – оновлення профілю користувача;
|
||||
– get_items() – отримання списку всіх товарів;
|
||||
– get_item_by_id() – отримання конкретного товару за ID;
|
||||
– get_cart() – отримання товарів у кошику з детальною інформацією;
|
||||
– get_cart_count() – підрахунок загальної кількості товарів у кошику;
|
||||
– get_cart_total() – підрахунок загальної вартості кошика;
|
||||
– add_to_cart() – додавання товару до кошика або оновлення кількості;
|
||||
– remove_from_cart() – видалення товару з кошика;
|
||||
– empty_cart() – повне очищення кошика.
|
||||
|
||||
3.4 Висновки
|
||||
|
||||
Під час виконання даної лабораторної роботи було успішно розроблено веб-
|
||||
застосунок інтернет-магазину з використанням сучасних веб-технологій. Зокрема,
|
||||
було освоєно:
|
||||
– створення багатосторінкових веб-застосунків на PHP;
|
||||
– роботу з базами даних SQLite через PDO з дотриманням принципів
|
||||
безпеки;
|
||||
– реалізацію сесійної системи для збереження стану користувача;
|
||||
– створення інтерактивного користувацького інтерфейсу з використанням
|
||||
HTML та CSS;
|
||||
– валідацію та фільтрацію користувацьких даних.
|
||||
Застосунок демонструє практичне застосування технологій веб-розробки для
|
||||
створення реальних рішень.
|
||||
6
|
||||
ДОДАТОК А
|
||||
Відеозапис
|
||||
|
||||
Відеозапис презентації результатів лабораторної роботи: https://youtu.be/Gils7poMkgk
|
||||
Хронологічний опис відеозапису:
|
||||
00:00 – Вступ та загальний опис роботи
|
||||
00:30 – Структура веб-застосунку
|
||||
01:14 – Робота з базою даних та сесіями
|
||||
01:43 – Обробка запитів користувача
|
||||
03:56 – Демонстрація роботи веб-застосунку
|
||||
7
|
||||
ДОДАТОК Б
|
||||
Програмний код
|
||||
|
||||
Б.1 Головна сторінка (index.php)
|
||||
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/index.php
|
||||
1 <?php
|
||||
2 session_start();
|
||||
3 require_once 'DB.php';
|
||||
4
|
||||
5 $db = new DB('shop.db');
|
||||
6
|
||||
7 if (!isset($_SESSION['user'])) {
|
||||
8 $_SESSION['user'] = $db->retrieve_user();
|
||||
9 }
|
||||
10
|
||||
11 $cart_count = $db->get_cart_count();
|
||||
12 ?>
|
||||
13
|
||||
14 <!DOCTYPE html>
|
||||
15 <html lang="uk">
|
||||
16 <head>
|
||||
17 <meta charset="UTF-8">
|
||||
18 <title>Головна сторінка</title>
|
||||
19 <link rel="stylesheet" href="style.css">
|
||||
20 </head>
|
||||
21 <body>
|
||||
22 <header>
|
||||
23 <h1>Продовольчий магазин "Весна"</h1>
|
||||
24 <h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
25 <nav>
|
||||
26 <a href="index.php">Головна</a>
|
||||
27 |
|
||||
28 <a href="items.php">Товари</a>
|
||||
29 |
|
||||
30 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
31 </nav>
|
||||
32 </header>
|
||||
33
|
||||
34 <div class="container" style="display: flex; flex-direction:
|
||||
column; align-items: center;">
|
||||
35 <img src="logo.png" alt="logo" style="width: 90%;">
|
||||
36 </div>
|
||||
37
|
||||
38 <footer>
|
||||
39 <nav>
|
||||
40 <a href="index.php">Головна</a>
|
||||
41 |
|
||||
42 <a href="items.php">Товари</a>
|
||||
43 |
|
||||
8
|
||||
|
||||
44 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
45 </nav>
|
||||
46 <p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права
|
||||
захищені.</p>
|
||||
47 </footer>
|
||||
48 </body>
|
||||
49 </html>
|
||||
|
||||
|
||||
Б.2 Каталог товарів (items.php)
|
||||
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/items.php
|
||||
1 <?php
|
||||
2 session_start();
|
||||
3 require_once 'DB.php';
|
||||
4
|
||||
5 $db = new DB('shop.db');
|
||||
6
|
||||
7 if (!isset($_SESSION['user'])) {
|
||||
8 $_SESSION['user'] = $db->retrieve_user();
|
||||
9 }
|
||||
10
|
||||
11 $items = $db->get_items();
|
||||
12 $cart_count = $db->get_cart_count();
|
||||
13 ?>
|
||||
14
|
||||
15 <!DOCTYPE html>
|
||||
16 <html lang="uk">
|
||||
17 <head>
|
||||
18 <meta charset="UTF-8">
|
||||
19 <title>Сторінка товарів</title>
|
||||
20 <link rel="stylesheet" href="style.css">
|
||||
21 </head>
|
||||
22 <body>
|
||||
23 <header>
|
||||
24 <h1>Продовольчий магазин "Весна"</h1>
|
||||
25 <h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
26 <nav>
|
||||
27 <a href="index.php">Головна</a>
|
||||
28 |
|
||||
29 <a href="items.php">Товари</a>
|
||||
30 |
|
||||
31 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
32 </nav>
|
||||
33 </header>
|
||||
34
|
||||
35 <div class="container">
|
||||
36 <h2>Доступні товари</h2>
|
||||
37 <div class="product-list">
|
||||
38 <?php foreach ($items as $item): ?>
|
||||
39 <div>
|
||||
9
|
||||
|
||||
40 <h2><?php echo htmlspecialchars($item['name']); ?
|
||||
></h2>
|
||||
41 <h3>Ціна: <?php echo number_format($item['price'],
|
||||
2); ?> грн</h3>
|
||||
42
|
||||
43 <form action="handle_cart.php" method="POST">
|
||||
44 <input type="hidden" name="product_id"
|
||||
value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
45 <label for="quantity_<?php echo
|
||||
htmlspecialchars($item['id']); ?>">Кількість:</label>
|
||||
46 <input type="number" id="quantity_<?php echo
|
||||
htmlspecialchars($item['id']); ?>" name="quantity" value="0" min="0"
|
||||
max="100">
|
||||
47 <button type="submit">Купити</button>
|
||||
48 </form>
|
||||
49 </div>
|
||||
50 <?php endforeach; ?>
|
||||
51 </div>
|
||||
52 </div>
|
||||
53
|
||||
54 <footer>
|
||||
55 <nav>
|
||||
56 <a href="index.php">Головна</a>
|
||||
57 |
|
||||
58 <a href="items.php">Товари</a>
|
||||
59 |
|
||||
60 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
61 </nav>
|
||||
62 <p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права
|
||||
захищені.</p>
|
||||
63 </footer>
|
||||
64 </body>
|
||||
65 </html>
|
||||
|
||||
|
||||
Б.3 Кошик покупок (cart.php)
|
||||
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/cart.php
|
||||
1 <?php
|
||||
2 session_start();
|
||||
3 require_once 'DB.php';
|
||||
4
|
||||
5 $db = new DB("shop.db");
|
||||
6
|
||||
7 if (!isset($_SESSION['user'])) {
|
||||
8 $_SESSION['user'] = $db->retrieve_user();
|
||||
9 }
|
||||
10
|
||||
11 $cart_items = $db->get_cart();
|
||||
12 $cart_total = $db->get_cart_total();
|
||||
13 $cart_count = $db->get_cart_count();
|
||||
14 ?>
|
||||
10
|
||||
|
||||
15
|
||||
16 <!DOCTYPE html>
|
||||
17 <html lang="uk">
|
||||
18 <head>
|
||||
19 <meta charset="UTF-8">
|
||||
20 <title>Кошик</title>
|
||||
21 <link rel="stylesheet" href="style.css">
|
||||
22 </head>
|
||||
23 <body>
|
||||
24 <header>
|
||||
25 <h1>Продовольчий магазин "Весна"</h1>
|
||||
26 <h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
27 <nav>
|
||||
28 <a href="index.php">Головна</a>
|
||||
29 |
|
||||
30 <a href="items.php">Товари</a>
|
||||
31 |
|
||||
32 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
33 </nav>
|
||||
34 </header>
|
||||
35
|
||||
36 <div class="container">
|
||||
37 <?php if (empty($cart_items)): ?>
|
||||
38 <div style="display: flex; align-items: center; justify-
|
||||
content: space-evenly;">
|
||||
39 <h3>Ваш кошик порожній <a href="items.php">Перейти до
|
||||
покупок</a> </h3>
|
||||
40 </div>
|
||||
41 <?php else: ?>
|
||||
42 <div style="display: flex; align-items: center; justify-
|
||||
content: space-evenly;">
|
||||
43 <h3>Ваш кошик</h3>
|
||||
44 <h3 class="cart-summary">
|
||||
45 Загальна сума: <?php echo
|
||||
number_format($cart_total, 2); ?> грн
|
||||
46 </h3>
|
||||
47
|
||||
48 <button type="submit">Сплатити</button>
|
||||
49
|
||||
50 <button onclick="fetch('handle_cart.php', {'method':
|
||||
'DELETE'}).then(_ => { location.reload(); });">Очистити</button>
|
||||
51 </div>
|
||||
52
|
||||
53 <div class="product-list">
|
||||
54 <?php foreach ($cart_items as $item): ?>
|
||||
55 <div>
|
||||
56 <h2>
|
||||
57 <?php echo
|
||||
htmlspecialchars($item['name']); ?>
|
||||
58 <br>
|
||||
59 <?php echo
|
||||
htmlspecialchars($item['count']); ?> шт.
|
||||
60 </h2>
|
||||
61
|
||||
11
|
||||
|
||||
62 <span>Ціна за одиницю: <?php echo
|
||||
number_format($item['price'], 2); ?> грн</span>
|
||||
63 <br>
|
||||
64 <span>Загальна ціна: <?php echo
|
||||
number_format($item['total_price'], 2); ?> грн</span>
|
||||
65
|
||||
66 <br><br>
|
||||
67
|
||||
68 <button
|
||||
69 style="width: 100%;"
|
||||
70 onclick="
|
||||
71 fetch(
|
||||
72 'handle_cart.php?product_id=<?php echo
|
||||
htmlspecialchars($item['id']); ?>',
|
||||
73 { 'method': 'DELETE' }
|
||||
74 ).then(_ => { location.reload(); });">
|
||||
75 Видалити
|
||||
76 </button>
|
||||
77 </div>
|
||||
78 <?php endforeach; ?>
|
||||
79 </div>
|
||||
80 <?php endif; ?>
|
||||
81 </div>
|
||||
82
|
||||
83 <footer>
|
||||
84 <nav>
|
||||
85 <a href="index.php">Головна</a>
|
||||
86 |
|
||||
87 <a href="items.php">Товари</a>
|
||||
88 |
|
||||
89 <a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?
|
||||
>)</a>
|
||||
90 </nav>
|
||||
91 <p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права
|
||||
захищені.</p>
|
||||
92 </footer>
|
||||
93 </body>
|
||||
94 </html>
|
||||
|
||||
|
||||
Б.4 Обробник кошика (handle_cart.php)
|
||||
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/handle_cart.php
|
||||
1 <?php
|
||||
2 session_start();
|
||||
3 require_once 'DB.php';
|
||||
4
|
||||
5 $db_path = 'shop.db';
|
||||
6 $db = new DB($db_path);
|
||||
7
|
||||
8 switch ($_SERVER['REQUEST_METHOD']) {
|
||||
9 case 'POST':
|
||||
12
|
||||
|
||||
10 $product_id = filter_input(INPUT_POST, 'product_id',
|
||||
FILTER_VALIDATE_INT);
|
||||
11 $quantity = filter_input(INPUT_POST, 'quantity',
|
||||
FILTER_VALIDATE_INT);
|
||||
12
|
||||
13 if ($product_id !== false && $product_id !== null &&
|
||||
$quantity !== false && $quantity !== null) {
|
||||
14 try {
|
||||
15 $db->add_to_cart($product_id, $quantity);
|
||||
16 } catch (DbException $e) {
|
||||
17 error_log("Cart handling error: " . $e->getMessage());
|
||||
18 }
|
||||
19 }
|
||||
20 header('Location: items.php');
|
||||
21 break;
|
||||
22
|
||||
23 case 'DELETE':
|
||||
24 $product_id = filter_input(INPUT_GET, 'product_id',
|
||||
FILTER_VALIDATE_INT);
|
||||
25
|
||||
26 if ($product_id === null) {
|
||||
27 try {
|
||||
28 $db->empty_cart();
|
||||
29 } catch (DbException $e) {
|
||||
30 error_log("Cart handling error: " . $e->getMessage());
|
||||
31 }
|
||||
32 } else if ($product_id !== false) {
|
||||
33 try {
|
||||
34 $db->remove_from_cart($product_id);
|
||||
35 } catch (DbException $e) {
|
||||
36 error_log("Cart handling error: " . $e->getMessage());
|
||||
37 }
|
||||
38 }
|
||||
39
|
||||
40 header('Location: cart.php');
|
||||
41 break;
|
||||
42
|
||||
43 default:
|
||||
44 break;
|
||||
45 }
|
||||
46
|
||||
47 exit();
|
||||
|
||||
|
||||
Б.5 Клас для роботи з базою даних (DB.php)
|
||||
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/DB.php
|
||||
1 <?php
|
||||
2
|
||||
3 class DbException extends Exception {}
|
||||
4
|
||||
5 class DB
|
||||
6 {
|
||||
13
|
||||
|
||||
7 private $pdo;
|
||||
8
|
||||
9 /**
|
||||
10 * Initializes database
|
||||
11 *
|
||||
12 * @param string $db_path
|
||||
13 * @throws DbException If there's a database error.
|
||||
14 */
|
||||
15 public function __construct($db_path)
|
||||
16 {
|
||||
17 try {
|
||||
18 $this->pdo = new PDO("sqlite:" . $db_path);
|
||||
19 $this->pdo->setAttribute(PDO::ATTR_ERRMODE,
|
||||
PDO::ERRMODE_EXCEPTION);
|
||||
20 } catch (PDOException $e) {
|
||||
21 throw new DbException("Connection to DB failed.\nCaused
|
||||
by: " . $e->getMessage());
|
||||
22 }
|
||||
23
|
||||
24 // Ініціалізація таблиць та початкових даних
|
||||
25 try {
|
||||
26 $this->pdo->exec("
|
||||
27 CREATE TABLE IF NOT EXISTS settings (
|
||||
28 name TEXT,
|
||||
29 age TEXT
|
||||
30 );
|
||||
31 ");
|
||||
32 if ($this->pdo->query("SELECT COUNT(*) FROM settings;")-
|
||||
>fetchColumn() == 0) {
|
||||
33 $this->pdo->exec("INSERT INTO settings (name, age)
|
||||
VALUES ('user', 0);");
|
||||
34 }
|
||||
35 } catch (PDOException $e) {
|
||||
36 throw new DbException("Error initialising settings table.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
37 }
|
||||
38
|
||||
39 try {
|
||||
40 $this->pdo->exec("
|
||||
41 CREATE TABLE IF NOT EXISTS items (
|
||||
42 id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
43 name TEXT NOT NULL,
|
||||
44 price REAL NOT NULL
|
||||
45 );
|
||||
46 ");
|
||||
47 if ($this->pdo->query("SELECT COUNT(id) FROM items;")-
|
||||
>fetchColumn() == 0) {
|
||||
48 $this->pdo->exec("
|
||||
49 INSERT INTO items (name, price) VALUES ('Молоко
|
||||
пастеризоване', 32.50);
|
||||
50 INSERT INTO items (name, price) VALUES ('Хліб
|
||||
чорний', 18.00);
|
||||
51 INSERT INTO items (name, price) VALUES ('Сир
|
||||
білий', 85.00);
|
||||
52 INSERT INTO items (name, price) VALUES ('Сметана
|
||||
20%', 45.80);
|
||||
14
|
||||
|
||||
53 INSERT INTO items (name, price) VALUES ('Кефір
|
||||
1%', 28.50);
|
||||
54 INSERT INTO items (name, price) VALUES ('Вода
|
||||
газована', 25.00);
|
||||
55 INSERT INTO items (name, price) VALUES ('Печиво
|
||||
\"Весна\"', 42.30);
|
||||
56 INSERT INTO items (name, price) VALUES ('Масло
|
||||
вершкове', 125.00);
|
||||
57 INSERT INTO items (name, price) VALUES ('Йогурт
|
||||
натуральний', 38.90);
|
||||
58 INSERT INTO items (name, price) VALUES ('Сік
|
||||
апельсиновий', 55.00);
|
||||
59 ");
|
||||
60 }
|
||||
61 } catch (PDOException $e) {
|
||||
62 throw new DbException("Error initialising items table.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
63 }
|
||||
64
|
||||
65 try {
|
||||
66 $this->pdo->exec("
|
||||
67 CREATE TABLE IF NOT EXISTS cart (
|
||||
68 id INTEGER NOT NULL UNIQUE,
|
||||
69 count INTEGER NOT NULL,
|
||||
70 FOREIGN KEY(id) REFERENCES items(id) ON DELETE
|
||||
CASCADE
|
||||
71 );
|
||||
72 ");
|
||||
73 } catch (PDOException $e) {
|
||||
74 throw new DbException("Error initialising cart table.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
75 }
|
||||
76 }
|
||||
77
|
||||
78 /**
|
||||
79 * Retrieve user information from the database
|
||||
80 *
|
||||
81 * @return array
|
||||
82 * @throws DbException If there's a database error.
|
||||
83 */
|
||||
84 public function retrieve_user(): array
|
||||
85 {
|
||||
86 try {
|
||||
87 $stmt = $this->pdo->query("SELECT name, age FROM
|
||||
settings;");
|
||||
88 return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
89 } catch (PDOException $e) {
|
||||
90 throw new DbException("Error retrieving user info.\nCaused
|
||||
by: " . $e->getMessage());
|
||||
91 }
|
||||
92 }
|
||||
93
|
||||
94 /**
|
||||
95 * Fetches all items from the database.
|
||||
96 *
|
||||
97 * @return array[]
|
||||
15
|
||||
|
||||
98 * @throws DbException If there's a database error.
|
||||
99 */
|
||||
100 public function get_items(): array
|
||||
101 {
|
||||
102 try {
|
||||
103 $stmt = $this->pdo->query("SELECT id, name, price FROM
|
||||
items ORDER BY id;");
|
||||
104 return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
105 } catch (PDOException $e) {
|
||||
106 throw new DbException("Error retrieving data from the
|
||||
items table.\nCaused by: " . $e->getMessage());
|
||||
107 }
|
||||
108 }
|
||||
109
|
||||
110 /**
|
||||
111 * Fetches all items in the cart from the database.
|
||||
112 *
|
||||
113 * @return array[]
|
||||
114 * @throws DbException If there's a database error.
|
||||
115 */
|
||||
116 public function get_cart(): array
|
||||
117 {
|
||||
118 try {
|
||||
119 $stmt = $this->pdo->query(
|
||||
120 "SELECT
|
||||
121 cart.id,
|
||||
122 items.name,
|
||||
123 items.price,
|
||||
124 cart.count,
|
||||
125 ROUND(items.price * cart.count, 2) as total_price
|
||||
126 FROM cart
|
||||
127 INNER JOIN items ON cart.id = items.id
|
||||
128 ORDER BY cart.id;"
|
||||
129 );
|
||||
130 return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
131 } catch (PDOException $e) {
|
||||
132 throw new DbException("Error retrieving cart items.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
133 }
|
||||
134 }
|
||||
135
|
||||
136 /**
|
||||
137 * Get total items count in cart
|
||||
138 *
|
||||
139 * @return int
|
||||
140 * @throws DbException If there's a database error.
|
||||
141 */
|
||||
142 public function get_cart_count(): int
|
||||
143 {
|
||||
144 try {
|
||||
145 $stmt = $this->pdo->query("SELECT COALESCE(SUM(count), 0)
|
||||
FROM cart;");
|
||||
146 return (int)$stmt->fetchColumn();
|
||||
147 } catch (PDOException $e) {
|
||||
148 throw new DbException("Error getting cart count.\nCaused
|
||||
by: " . $e->getMessage());
|
||||
16
|
||||
|
||||
149 }
|
||||
150 }
|
||||
151
|
||||
152 /**
|
||||
153 * Get total price of all items in cart
|
||||
154 *
|
||||
155 * @return float
|
||||
156 * @throws DbException If there's a database error.
|
||||
157 */
|
||||
158 public function get_cart_total(): float
|
||||
159 {
|
||||
160 try {
|
||||
161 $stmt = $this->pdo->query(
|
||||
162 "SELECT COALESCE(SUM(items.price * cart.count), 0.0)
|
||||
163 FROM cart
|
||||
164 INNER JOIN items ON cart.id = items.id;"
|
||||
165 );
|
||||
166 return (float)$stmt->fetchColumn();
|
||||
167 } catch (PDOException $e) {
|
||||
168 throw new DbException("Error calculating cart total.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
169 }
|
||||
170 }
|
||||
171
|
||||
172 /**
|
||||
173 * Add item to the cart or update its quantity.
|
||||
174 *
|
||||
175 * @param int $id
|
||||
176 * @param int $count
|
||||
177 *
|
||||
178 * @return bool
|
||||
179 * @throws DbException If there's a database error or item doesn't
|
||||
exist.
|
||||
180 */
|
||||
181 public function add_to_cart($id, $count): bool
|
||||
182 {
|
||||
183 try {
|
||||
184 // Check if the item exists
|
||||
185 $item = $this->get_item_by_id($id);
|
||||
186 if (!$item) {
|
||||
187 throw new DbException("Item with ID $id does not
|
||||
exist.");
|
||||
188 }
|
||||
189
|
||||
190 // If count is 0 or less, remove the item from the cart
|
||||
191 if ($count <= 0) {
|
||||
192 return $this->remove_from_cart($id);
|
||||
193 }
|
||||
194
|
||||
195 // Insert or update the cart item
|
||||
196 $stmt = $this->pdo->prepare(
|
||||
197 "INSERT INTO cart (id, count)
|
||||
198 VALUES (:id, :count)
|
||||
199 ON CONFLICT(id) DO UPDATE SET
|
||||
200 count = excluded.count;"
|
||||
201 );
|
||||
17
|
||||
|
||||
202 return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
203 } catch (PDOException $e) {
|
||||
204 throw new DbException("Error adding/updating item in the
|
||||
cart.\nCaused by: " . $e->getMessage());
|
||||
205 }
|
||||
206 }
|
||||
207
|
||||
208 /**
|
||||
209 * Empty the cart
|
||||
210 *
|
||||
211 * @return bool
|
||||
212 * @throws DbException If there's a database error.
|
||||
213 */
|
||||
214 public function empty_cart(): bool
|
||||
215 {
|
||||
216 try {
|
||||
217 $stmt = $this->pdo->prepare("DELETE FROM cart");
|
||||
218 return $stmt->execute();
|
||||
219 } catch (PDOException $e) {
|
||||
220 throw new DbException("Error removing item from the cart.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
221 }
|
||||
222 }
|
||||
223
|
||||
224 /**
|
||||
225 * Remove an item from the cart
|
||||
226 *
|
||||
227 * @param int $id
|
||||
228 *
|
||||
229 * @return bool
|
||||
230 * @throws DbException If there's a database error.
|
||||
231 */
|
||||
232 public function remove_from_cart($id): bool
|
||||
233 {
|
||||
234 try {
|
||||
235 $stmt = $this->pdo->prepare("DELETE FROM cart WHERE id
|
||||
= :id");
|
||||
236 return $stmt->execute(['id' => $id]);
|
||||
237 } catch (PDOException $e) {
|
||||
238 throw new DbException("Error removing item from the cart.
|
||||
\nCaused by: " . $e->getMessage());
|
||||
239 }
|
||||
240 }
|
||||
241 }
|
||||
|
712
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.typ
Normal file
712
semester-4/СмП/lb-3/Лр_3_Ситник_ПЗПІ_23_2.typ
Normal file
@ -0,0 +1,712 @@
|
||||
#import "@local/nure:0.1.0": *
|
||||
|
||||
#show: pz-lb.with(
|
||||
title: [Створення WEB-застосунків за допомогою PHP],
|
||||
subject: "СМП",
|
||||
doctype: "ЛБ",
|
||||
worknumber: 3,
|
||||
edu_program: "ПЗПІ",
|
||||
university: "ХНУРЕ",
|
||||
mentors: (
|
||||
(
|
||||
name: "Сокорчук І. П.",
|
||||
degree: "Старший викладач кафедри ПІ",
|
||||
gender: "m",
|
||||
),
|
||||
),
|
||||
authors: (
|
||||
(
|
||||
name: "Ситник Є. С.",
|
||||
course: 2,
|
||||
edu: "ПЗПІ",
|
||||
gender: "m",
|
||||
group: "23-2",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
#v(-spacing)
|
||||
== Історія змін
|
||||
#figure(
|
||||
table(
|
||||
align: left,
|
||||
columns: (auto, 1fr, 1fr, 3fr),
|
||||
table.header([№], [Дата], [Версія звіту], [Опис змін та виправлень]),
|
||||
[1], [03.06.2025], [0.1], [Створено звіт],
|
||||
),
|
||||
)
|
||||
|
||||
== Мета роботи
|
||||
Лабораторна робота полягає у розробці веб-застосунку "Продовольчий магазин Весна" засобами мови програмування PHP з використанням HTML, CSS та JavaScript для створення інтерактивного інтерфейсу користувача.
|
||||
|
||||
== Хід роботи
|
||||
#v(-spacing)
|
||||
=== Архітектура веб-застосунку
|
||||
|
||||
Для реалізації веб-застосунку було обрано модульний підхід з розділенням логіки на окремі файли:
|
||||
|
||||
==== Структура файлів:
|
||||
|
||||
- *index.php* -- головна сторінка застосунку з логотипом магазину;
|
||||
- *items.php* -- сторінка каталогу товарів з можливістю додавання до кошика;
|
||||
- *cart.php* -- сторінка кошика з переглядом обраних товарів та управлінням;
|
||||
- *handle_cart.php* -- обробник AJAX-запитів для роботи з кошиком;
|
||||
- *DB.php* -- клас для роботи з базою даних SQLite;
|
||||
- *style.css* -- стилі для оформлення веб-сторінок.
|
||||
|
||||
==== Основні компоненти:
|
||||
|
||||
- *Клас DB* -- забезпечує взаємодію з базою даних SQLite, включаючи методи для роботи з товарами, кошиком та налаштуваннями користувача;
|
||||
- *Клас DbException* -- спеціалізований виняток для обробки помилок бази даних;
|
||||
- *Сесійна система* -- для збереження стану користувача між сторінками.
|
||||
|
||||
=== Структура бази даних
|
||||
|
||||
Веб-застосунок використовує базу даних SQLite з трьома основними таблицями:
|
||||
|
||||
==== Таблиця settings:
|
||||
- *name* (TEXT) -- ім'я користувача;
|
||||
- *age* (TEXT) -- вік користувача.
|
||||
|
||||
==== Таблиця items:
|
||||
- *id* (INTEGER PRIMARY KEY) -- унікальний ідентифікатор товару;
|
||||
- *name* (TEXT NOT NULL) -- назва товару;
|
||||
- *price* (REAL NOT NULL) -- ціна товару.
|
||||
|
||||
==== Таблиця cart:
|
||||
- *id* (INTEGER NOT NULL UNIQUE) -- ідентифікатор товару;
|
||||
- *count* (INTEGER NOT NULL) -- кількість товару в кошику;
|
||||
- *FOREIGN KEY* -- зв'язок з таблицею items.
|
||||
|
||||
=== Функціональні можливості
|
||||
|
||||
==== Головна сторінка (index.php):
|
||||
- Відображення логотипу магазину;
|
||||
- Навігаційне меню з кількістю товарів у кошику;
|
||||
- Привітання користувача за ім'ям;
|
||||
- Підвал з інформацією про магазин.
|
||||
|
||||
==== Каталог товарів (items.php):
|
||||
- Відображення списку доступних товарів з цінами;
|
||||
- Форма для вибору кількості товару;
|
||||
- Можливість додавання товарів до кошика;
|
||||
- Валідація кількості товару (від 0 до 100 одиниць);
|
||||
- Автоматичне оновлення лічильника кошика.
|
||||
|
||||
==== Кошик покупок (cart.php):
|
||||
- Перегляд обраних товарів з детальною інформацією;
|
||||
- Відображення кількості, ціни за одиницю та загальної вартості;
|
||||
- Можливість видалення окремих товарів з кошика;
|
||||
- Функція повного очищення кошика;
|
||||
- Підрахунок загальної суми покупки;
|
||||
- Кнопка "Сплатити" для завершення покупки.
|
||||
|
||||
==== Обробка запитів (handle_cart.php):
|
||||
- Обробка POST-запитів для додавання товарів до кошика;
|
||||
- Обробка DELETE-запитів для видалення товарів;
|
||||
- Підтримка AJAX для асинхронного оновлення кошика;
|
||||
- Валідація вхідних даних та обробка помилок.
|
||||
|
||||
=== Технічні особливості
|
||||
|
||||
==== Безпека:
|
||||
- Використання підготовлених запитів (prepared statements) для запобігання SQL-ін'єкціям;
|
||||
- Функція "htmlspecialchars()" для запобігання XSS-атакам;
|
||||
- Валідація та фільтрація користувацьких даних через "filter_input()";
|
||||
- Обробка винятків для коректної роботи з помилками.
|
||||
|
||||
==== Користувацький інтерфейс:
|
||||
- Адаптивний дизайн з використанням CSS;
|
||||
- Інтуїтивна навігація між сторінками;
|
||||
- Зручні форми для взаємодії з користувачем.
|
||||
|
||||
==== База даних:
|
||||
- Автоматичне створення таблиць при першому запуску;
|
||||
- Заповнення початковими даними;
|
||||
|
||||
=== Методи класу DB
|
||||
|
||||
Клас DB містить наступні основні методи:
|
||||
|
||||
- *retrieve_user()* -- отримання інформації про користувача;
|
||||
- *update_user()* -- оновлення профілю користувача;
|
||||
- *get_items()* -- отримання списку всіх товарів;
|
||||
- *get_item_by_id()* -- отримання конкретного товару за ID;
|
||||
- *get_cart()* -- отримання товарів у кошику з детальною інформацією;
|
||||
- *get_cart_count()* -- підрахунок загальної кількості товарів у кошику;
|
||||
- *get_cart_total()* -- підрахунок загальної вартості кошика;
|
||||
- *add_to_cart()* -- додавання товару до кошика або оновлення кількості;
|
||||
- *remove_from_cart()* -- видалення товару з кошика;
|
||||
- *empty_cart()* -- повне очищення кошика.
|
||||
|
||||
== Висновки
|
||||
|
||||
Під час виконання даної лабораторної роботи було успішно розроблено веб-застосунок інтернет-магазину з використанням сучасних веб-технологій. Зокрема, було освоєно:
|
||||
|
||||
- створення багатосторінкових веб-застосунків на PHP;
|
||||
- роботу з базами даних SQLite через PDO з дотриманням принципів безпеки;
|
||||
- реалізацію сесійної системи для збереження стану користувача;
|
||||
- створення інтерактивного користувацького інтерфейсу з використанням HTML та CSS;
|
||||
- валідацію та фільтрацію користувацьких даних.
|
||||
|
||||
Застосунок демонструє практичне застосування технологій веб-розробки для створення реальних рішень.
|
||||
|
||||
#set raw(tab-size: 8)
|
||||
#show raw.where(block: true): code => {
|
||||
set text(11pt, top-edge: 1em, bottom-edge: 0em)
|
||||
set par(leading: 0.17em)
|
||||
|
||||
grid(
|
||||
columns: (auto, auto),
|
||||
column-gutter: 1em,
|
||||
row-gutter: 0.17em,
|
||||
align: (right, raw.align),
|
||||
..for line in code.lines {
|
||||
(
|
||||
text(fill: gray)[#line.number],
|
||||
{
|
||||
set text(weight: "semibold")
|
||||
line.body
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
#show: appendices_style
|
||||
= Відеозапис
|
||||
Відеозапис презентації результатів лабораторної роботи: https://youtu.be/Gils7poMkgk
|
||||
|
||||
*Хронологічний опис відеозапису:*
|
||||
|
||||
00:00 -- Вступ та загальний опис роботи
|
||||
|
||||
00:30 -- Структура веб-застосунку
|
||||
|
||||
01:14 -- Робота з базою даних та сесіями
|
||||
|
||||
01:43 -- Обробка запитів користувача
|
||||
|
||||
03:56 -- Демонстрація роботи веб-застосунку
|
||||
|
||||
= Програмний код
|
||||
|
||||
== Головна сторінка (index.php)
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/index.php
|
||||
```
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'DB.php';
|
||||
|
||||
$db = new DB('shop.db');
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
$_SESSION['user'] = $db->retrieve_user();
|
||||
}
|
||||
|
||||
$cart_count = $db->get_cart_count();
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Головна сторінка</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Продовольчий магазин "Весна"</h1>
|
||||
<h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="container" style="display: flex; flex-direction: column; align-items: center;">
|
||||
<img src="logo.png" alt="logo" style="width: 90%;">
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
<p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
== Каталог товарів (items.php)
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/items.php
|
||||
```
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'DB.php';
|
||||
|
||||
$db = new DB('shop.db');
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
$_SESSION['user'] = $db->retrieve_user();
|
||||
}
|
||||
|
||||
$items = $db->get_items();
|
||||
$cart_count = $db->get_cart_count();
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Сторінка товарів</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Продовольчий магазин "Весна"</h1>
|
||||
<h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<h2>Доступні товари</h2>
|
||||
<div class="product-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div>
|
||||
<h2><?php echo htmlspecialchars($item['name']); ?></h2>
|
||||
<h3>Ціна: <?php echo number_format($item['price'], 2); ?> грн</h3>
|
||||
|
||||
<form action="handle_cart.php" method="POST">
|
||||
<input type="hidden" name="product_id" value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
<label for="quantity_<?php echo htmlspecialchars($item['id']); ?>">Кількість:</label>
|
||||
<input type="number" id="quantity_<?php echo htmlspecialchars($item['id']); ?>" name="quantity" value="0" min="0" max="100">
|
||||
<button type="submit">Купити</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
<p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
== Кошик покупок (cart.php)
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/cart.php
|
||||
```
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'DB.php';
|
||||
|
||||
$db = new DB("shop.db");
|
||||
|
||||
if (!isset($_SESSION['user'])) {
|
||||
$_SESSION['user'] = $db->retrieve_user();
|
||||
}
|
||||
|
||||
$cart_items = $db->get_cart();
|
||||
$cart_total = $db->get_cart_total();
|
||||
$cart_count = $db->get_cart_count();
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Кошик</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Продовольчий магазин "Весна"</h1>
|
||||
<h3> Добрий день <?php echo $_SESSION['user']['name'] ?> </h3>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<?php if (empty($cart_items)): ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик порожній <a href="items.php">Перейти до покупок</a> </h3>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик</h3>
|
||||
<h3 class="cart-summary">
|
||||
Загальна сума: <?php echo number_format($cart_total, 2); ?> грн
|
||||
</h3>
|
||||
|
||||
<button type="submit">Сплатити</button>
|
||||
|
||||
<button onclick="fetch('handle_cart.php', {'method': 'DELETE'}).then(_ => { location.reload(); });">Очистити</button>
|
||||
</div>
|
||||
|
||||
<div class="product-list">
|
||||
<?php foreach ($cart_items as $item): ?>
|
||||
<div>
|
||||
<h2>
|
||||
<?php echo htmlspecialchars($item['name']); ?>
|
||||
<br>
|
||||
<?php echo htmlspecialchars($item['count']); ?> шт.
|
||||
</h2>
|
||||
|
||||
<span>Ціна за одиницю: <?php echo number_format($item['price'], 2); ?> грн</span>
|
||||
<br>
|
||||
<span>Загальна ціна: <?php echo number_format($item['total_price'], 2); ?> грн</span>
|
||||
|
||||
<br><br>
|
||||
|
||||
<button
|
||||
style="width: 100%;"
|
||||
onclick="
|
||||
fetch(
|
||||
'handle_cart.php?product_id=<?php echo htmlspecialchars($item['id']); ?>',
|
||||
{ 'method': 'DELETE' }
|
||||
).then(_ => { location.reload(); });">
|
||||
Видалити
|
||||
</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<nav>
|
||||
<a href="index.php">Головна</a>
|
||||
|
|
||||
<a href="items.php">Товари</a>
|
||||
|
|
||||
<a href="cart.php">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
</nav>
|
||||
<p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
== Обробник кошика (handle_cart.php)
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/handle_cart.php
|
||||
```
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'DB.php';
|
||||
|
||||
$db_path = 'shop.db';
|
||||
$db = new DB($db_path);
|
||||
|
||||
switch ($_SERVER['REQUEST_METHOD']) {
|
||||
case 'POST':
|
||||
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
|
||||
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id !== false && $product_id !== null && $quantity !== false && $quantity !== null) {
|
||||
try {
|
||||
$db->add_to_cart($product_id, $quantity);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
header('Location: items.php');
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$product_id = filter_input(INPUT_GET, 'product_id', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id === null) {
|
||||
try {
|
||||
$db->empty_cart();
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
} else if ($product_id !== false) {
|
||||
try {
|
||||
$db->remove_from_cart($product_id);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: cart.php');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
exit();
|
||||
```
|
||||
|
||||
== Клас для роботи з базою даних (DB.php)
|
||||
GitHub репозиторій: https://github.com/NureSytnykYehor/smp-pzpi-23-2-sytnyk-yehor/blob/main/Lab3/smp-pzpi-23-2-sytnyk-yehor-lab3/DB.php
|
||||
```
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Ініціалізація таблиць та початкових даних
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
name TEXT,
|
||||
age TEXT
|
||||
);
|
||||
");
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM settings;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("INSERT INTO settings (name, age) VALUES ('user', 0);");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising settings table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 32.50);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 18.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 85.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 45.80);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 28.50);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 25.00);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 42.30);
|
||||
INSERT INTO items (name, price) VALUES ('Масло вершкове', 125.00);
|
||||
INSERT INTO items (name, price) VALUES ('Йогурт натуральний', 38.90);
|
||||
INSERT INTO items (name, price) VALUES ('Сік апельсиновий', 55.00);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve user information from the database
|
||||
*
|
||||
* @return array
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function retrieve_user(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT name, age FROM settings;");
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving user info.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id,
|
||||
items.name,
|
||||
items.price,
|
||||
cart.count,
|
||||
ROUND(items.price * cart.count, 2) as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving cart items.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total items count in cart
|
||||
*
|
||||
* @return int
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_count(): int
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT COALESCE(SUM(count), 0) FROM cart;");
|
||||
return (int)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error getting cart count.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total price of all items in cart
|
||||
*
|
||||
* @return float
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_total(): float
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT COALESCE(SUM(items.price * cart.count), 0.0)
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id;"
|
||||
);
|
||||
return (float)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error calculating cart total.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart or update its quantity.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error or item doesn't exist.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
// Check if the item exists
|
||||
$item = $this->get_item_by_id($id);
|
||||
if (!$item) {
|
||||
throw new DbException("Item with ID $id does not exist.");
|
||||
}
|
||||
|
||||
// If count is 0 or less, remove the item from the cart
|
||||
if ($count <= 0) {
|
||||
return $this->remove_from_cart($id);
|
||||
}
|
||||
|
||||
// Insert or update the cart item
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart (id, count)
|
||||
VALUES (:id, :count)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
count = excluded.count;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding/updating item in the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the cart
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function empty_cart(): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart");
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
4
semester-4/СмП/lb-4/README.md
Normal file
4
semester-4/СмП/lb-4/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
> [!NOTE]
|
||||
> Викладач: Сокорчук І. П.
|
||||
>
|
||||
> Оцінка: In Progress
|
92
semester-4/СмП/lb-4/src/index.php
Normal file
92
semester-4/СмП/lb-4/src/index.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
session_start();
|
||||
|
||||
require_once 'src/Database/DB.php';
|
||||
require_once 'src/Database/CartRepository.php';
|
||||
require_once 'src/Controllers/HomeController.php';
|
||||
require_once 'src/Controllers/ItemsController.php';
|
||||
require_once 'src/Controllers/CartController.php';
|
||||
require_once 'src/Controllers/AuthController.php';
|
||||
require_once 'src/Controllers/ProfileController.php';
|
||||
|
||||
$db = new DB("shop.db");
|
||||
|
||||
$request = $_GET['page'] ?? 'home';
|
||||
$action = $_GET['action'] ?? 'index';
|
||||
|
||||
$protected_pages = ['home', 'items', 'cart', 'profile'];
|
||||
$public_pages = ['login', 'register'];
|
||||
|
||||
if (in_array($request, $protected_pages) && !isset($_SESSION['user'])) {
|
||||
header('Location: ?page=404');
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($request) {
|
||||
case 'home':
|
||||
$controller = new HomeController($db);
|
||||
$controller->index();
|
||||
break;
|
||||
|
||||
case 'items':
|
||||
$controller = new ItemsController($db);
|
||||
$controller->index();
|
||||
break;
|
||||
|
||||
case 'cart':
|
||||
$controller = new CartController($db);
|
||||
if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->add();
|
||||
} elseif ($action === 'remove' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->remove();
|
||||
} elseif ($action === 'clear' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->clear();
|
||||
} else {
|
||||
$controller->index();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'login':
|
||||
$controller = new AuthController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->login();
|
||||
} else {
|
||||
$controller->showLogin();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
$controller = new AuthController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->register();
|
||||
} else {
|
||||
$controller->showRegister();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
$controller = new AuthController($db);
|
||||
$controller->logout();
|
||||
break;
|
||||
|
||||
case 'profile':
|
||||
$controller = new ProfileController($db);
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$controller->updateProfile();
|
||||
} else {
|
||||
$controller->showProfile();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
include 'templates/pages/404.php';
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Application error: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
include 'templates/pages/error.php';
|
||||
}
|
BIN
semester-4/СмП/lb-4/src/public/assets/logo.png
Normal file
BIN
semester-4/СмП/lb-4/src/public/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 MiB |
43
semester-4/СмП/lb-4/src/public/css/style.css
Normal file
43
semester-4/СмП/lb-4/src/public/css/style.css
Normal file
@ -0,0 +1,43 @@
|
||||
body {
|
||||
font-family: monospace;
|
||||
background-color: #f0f0f0;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav a:not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.product-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.product-list>* {
|
||||
border: 1px solid #eee;
|
||||
padding: 10px;
|
||||
}
|
109
semester-4/СмП/lb-4/src/src/Controllers/AuthController.php
Normal file
109
semester-4/СмП/lb-4/src/src/Controllers/AuthController.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
class AuthController
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function showLogin(): void
|
||||
{
|
||||
if (isset($_SESSION['user'])) {
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = ['title' => 'Вхід в систему'];
|
||||
$this->render('login', $data);
|
||||
}
|
||||
|
||||
public function login(): void
|
||||
{
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$error = '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
$error = 'Заповніть всі поля';
|
||||
} else {
|
||||
try {
|
||||
$user = $this->db->authenticate_user($username, $password);
|
||||
if ($user) {
|
||||
$_SESSION['user'] = $user;
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
} else {
|
||||
$error = 'Невірні дані для входу';
|
||||
}
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка системи';
|
||||
error_log("Login error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$data = ['title' => 'Вхід в систему', 'error' => $error];
|
||||
$this->render('login', $data);
|
||||
}
|
||||
|
||||
public function showRegister(): void
|
||||
{
|
||||
if (isset($_SESSION['user'])) {
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = ['title' => 'Реєстрація'];
|
||||
$this->render('register', $data);
|
||||
}
|
||||
|
||||
public function register(): void
|
||||
{
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$surname = trim($_POST['surname'] ?? '');
|
||||
$age = (int)($_POST['age'] ?? 0);
|
||||
$error = '';
|
||||
|
||||
if (empty($username) || empty($password) || empty($name)) {
|
||||
$error = 'Заповніть всі обов\'язкові поля';
|
||||
} elseif (strlen($password) < 6) {
|
||||
$error = 'Пароль повинен містити мінімум 6 символів';
|
||||
} else {
|
||||
try {
|
||||
if ($this->db->register_user($username, $password, $name, $surname, $age)) {
|
||||
$user = $this->db->authenticate_user($username, $password);
|
||||
$_SESSION['user'] = $user;
|
||||
header('Location: ?page=home');
|
||||
exit();
|
||||
} else {
|
||||
$error = 'Користувач з таким іменем вже існує';
|
||||
}
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка реєстрації';
|
||||
error_log("Registration error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$data = ['title' => 'Реєстрація', 'error' => $error];
|
||||
$this->render('register', $data);
|
||||
}
|
||||
|
||||
public function logout(): void
|
||||
{
|
||||
session_destroy();
|
||||
header('Location: ?page=login');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
77
semester-4/СмП/lb-4/src/src/Controllers/CartController.php
Normal file
77
semester-4/СмП/lb-4/src/src/Controllers/CartController.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
class CartController
|
||||
{
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Кошик',
|
||||
'cart_items' => $this->cartRepo->getItems(),
|
||||
'cart_total' => $this->cartRepo->getTotal(),
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('cart', $data);
|
||||
}
|
||||
|
||||
public function add(): void
|
||||
{
|
||||
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
|
||||
$quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id !== false && $product_id !== null && $quantity !== false && $quantity !== null) {
|
||||
try {
|
||||
$this->cartRepo->addItem($product_id, $quantity);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ?page=items');
|
||||
exit();
|
||||
}
|
||||
|
||||
public function remove(): void
|
||||
{
|
||||
$product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT);
|
||||
|
||||
if ($product_id !== false && $product_id !== null) {
|
||||
try {
|
||||
$this->cartRepo->removeItem($product_id);
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: ?page=cart');
|
||||
exit();
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
try {
|
||||
$this->cartRepo->clear();
|
||||
} catch (DbException $e) {
|
||||
error_log("Cart handling error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
header('Location: ?page=cart');
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
30
semester-4/СмП/lb-4/src/src/Controllers/HomeController.php
Normal file
30
semester-4/СмП/lb-4/src/src/Controllers/HomeController.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
class HomeController
|
||||
{
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Головна сторінка',
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('home', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
33
semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php
Normal file
33
semester-4/СмП/lb-4/src/src/Controllers/ItemsController.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class ItemsController
|
||||
{
|
||||
private $db;
|
||||
private $cartRepo;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->cartRepo = new CartRepository($db);
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$data = [
|
||||
'title' => 'Сторінка товарів',
|
||||
'items' => $this->db->get_items(),
|
||||
'cart_count' => $this->cartRepo->getCount(),
|
||||
'user' => $_SESSION['user']
|
||||
];
|
||||
|
||||
$this->render('items', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
class ProfileController
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function showProfile(): void
|
||||
{
|
||||
$data = ['title' => 'Профіль'];
|
||||
$this->render('profile', $data);
|
||||
}
|
||||
|
||||
public function updateProfile(): void
|
||||
{
|
||||
$user = $_SESSION['user'];
|
||||
$id = $user['id'];
|
||||
$name = trim($_POST['name'] ?? $user['name']);
|
||||
$surname = trim($_POST['surname'] ?? $user['surname']);
|
||||
$age = (int)($_POST['age'] ?? $user['age']);
|
||||
$description = trim($_POST['description'] ?? $user['description']);
|
||||
$photo_path = $user['photo_path'];
|
||||
$error = '';
|
||||
|
||||
if (mb_strlen($name) < 2 && mb_strlen($surname) < 2) {
|
||||
$error = "Ім'я та прізвище повинні мати довжину більше 1 символа";
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb_strlen($description) < 50) {
|
||||
$error = 'Біоаграфія не може бути менше 50 символів';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
|
||||
if (!in_array($_FILES['photo']['type'], ['image/jpeg', 'image/png'])) {
|
||||
$error = 'Неправильний формат файлу';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$uploads = 'uploads/';
|
||||
if (!is_dir($uploads)) {
|
||||
mkdir($uploads, 0755, true);
|
||||
}
|
||||
|
||||
$new_path = $uploads . $id . '-' . time() . '.' . pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION);
|
||||
|
||||
if (!move_uploaded_file($_FILES['photo']['tmp_name'], $new_path)) {
|
||||
$error = 'Помилка під час переміщення файлу';
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
return;
|
||||
}
|
||||
|
||||
$photo_path = $new_path;
|
||||
}
|
||||
|
||||
try {
|
||||
$success = $this->db->update_user($id, $name, $surname, $description, $photo_path, $age);
|
||||
|
||||
if ($success) {
|
||||
$user = $this->db->get_user_by_id($id);
|
||||
$_SESSION['user'] = $user;
|
||||
} else {
|
||||
$error = 'Під час оновлення даних сталася помилка';
|
||||
}
|
||||
|
||||
header('Location: ?page=profile');
|
||||
exit();
|
||||
} catch (DbException $e) {
|
||||
$error = 'Помилка оновлення профілю';
|
||||
error_log("Profile update error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$data = ['title' => 'Профіль', 'error' => $error];
|
||||
$this->render('profile', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $data
|
||||
*/
|
||||
private function render(string $template, array $data = []): void
|
||||
{
|
||||
extract($data);
|
||||
include 'templates/pages/' . $template . '.php';
|
||||
}
|
||||
}
|
45
semester-4/СмП/lb-4/src/src/Database/CartRepository.php
Normal file
45
semester-4/СмП/lb-4/src/src/Database/CartRepository.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class CartRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct(DB $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->db->get_cart();
|
||||
}
|
||||
|
||||
public function getTotal(): float
|
||||
{
|
||||
return $this->db->get_cart_total();
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->db->get_cart_count();
|
||||
}
|
||||
|
||||
public function addItem(int $id, int $quantity): bool
|
||||
{
|
||||
return $this->db->add_to_cart($id, $quantity);
|
||||
}
|
||||
|
||||
public function removeItem(int $id): bool
|
||||
{
|
||||
return $this->db->remove_from_cart($id);
|
||||
}
|
||||
|
||||
public function clear(): bool
|
||||
{
|
||||
return $this->db->empty_cart();
|
||||
}
|
||||
|
||||
public function getItemQuantity(int $id): int
|
||||
{
|
||||
return $this->db->get_cart_item_quantity($id);
|
||||
}
|
||||
}
|
394
semester-4/СмП/lb-4/src/src/Database/DB.php
Normal file
394
semester-4/СмП/lb-4/src/src/Database/DB.php
Normal file
@ -0,0 +1,394 @@
|
||||
<?php
|
||||
|
||||
class DbException extends Exception {}
|
||||
|
||||
class DB
|
||||
{
|
||||
private $pdo;
|
||||
|
||||
/**
|
||||
* Initializes database
|
||||
*
|
||||
* @param string $db_path
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function __construct($db_path)
|
||||
{
|
||||
try {
|
||||
$this->pdo = new PDO("sqlite:" . $db_path);
|
||||
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Connection to DB failed.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
surname TEXT NOT NULL,
|
||||
description TEXT NULL,
|
||||
photo_path TEXT NULL,
|
||||
age INTEGER DEFAULT 0
|
||||
);
|
||||
");
|
||||
|
||||
if ($this->pdo->query("SELECT COUNT(*) FROM users;")->fetchColumn() == 0) {
|
||||
$default_password = password_hash('admin123', PASSWORD_DEFAULT);
|
||||
$this->pdo->exec("
|
||||
INSERT INTO users (username, password, name, surname, description, age)
|
||||
VALUES ('admin', '$default_password', 'Адміністратор', 'Адміністратор', 'Адміністратор', 25);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising users table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
price REAL NOT NULL
|
||||
);
|
||||
");
|
||||
if ($this->pdo->query("SELECT COUNT(id) FROM items;")->fetchColumn() == 0) {
|
||||
$this->pdo->exec("
|
||||
INSERT INTO items (name, price) VALUES ('Молоко пастеризоване', 32.50);
|
||||
INSERT INTO items (name, price) VALUES ('Хліб чорний', 18.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сир білий', 85.00);
|
||||
INSERT INTO items (name, price) VALUES ('Сметана 20%', 45.80);
|
||||
INSERT INTO items (name, price) VALUES ('Кефір 1%', 28.50);
|
||||
INSERT INTO items (name, price) VALUES ('Вода газована', 25.00);
|
||||
INSERT INTO items (name, price) VALUES ('Печиво \"Весна\"', 42.30);
|
||||
INSERT INTO items (name, price) VALUES ('Масло вершкове', 125.00);
|
||||
INSERT INTO items (name, price) VALUES ('Йогурт натуральний', 38.90);
|
||||
INSERT INTO items (name, price) VALUES ('Сік апельсиновий', 55.00);
|
||||
");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS cart (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
count INTEGER NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES items(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error initialising cart table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates user by id
|
||||
* @param int $id
|
||||
* @param string $name
|
||||
* @param string $surname
|
||||
* @param string $description
|
||||
* @param int $age
|
||||
* @param string $photo_path
|
||||
*/
|
||||
public function update_user($id, $name, $surname, $description, $photo_path, $age): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE users
|
||||
SET name = :name, surname = :surname, description = :description, age = :age, photo_path = :photo_path
|
||||
WHERE id = :id
|
||||
");
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':surname', $surname, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':description', $description, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':photo_path', $photo_path, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error updating user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user by username and password
|
||||
* @param mixed $username
|
||||
* @param mixed $password
|
||||
*/
|
||||
public function authenticate_user($username, $password): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, name, surname, description, photo_path, age FROM users WHERE username = :username");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user) {
|
||||
$stmt = $this->pdo->prepare("SELECT password FROM users WHERE username = :username");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->execute();
|
||||
$stored_password = $stmt->fetchColumn();
|
||||
|
||||
if (password_verify($password, $stored_password)) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error authenticating user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
* @param mixed $username
|
||||
* @param mixed $password
|
||||
* @param mixed $name
|
||||
* @param mixed $surname
|
||||
* @param mixed $age
|
||||
*/
|
||||
public function register_user($username, $password, $name, $surname, $age): bool
|
||||
{
|
||||
try {
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $this->pdo->prepare("INSERT INTO users (username, password, name, surname, age) VALUES (:username, :password, :name, :surname, :age)");
|
||||
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':password', $hashed_password, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':name', $name, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':surname', $surname, PDO::PARAM_STR);
|
||||
$stmt->bindParam(':age', $age, PDO::PARAM_INT);
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error registering user.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function get_user_by_id($id): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, username, name, surname, description, age, photo_path FROM users WHERE id = :id");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving user by ID.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_items(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT id, name, price FROM items ORDER BY id;");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving data from the items table.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a specific item by ID
|
||||
*
|
||||
* @param int $id
|
||||
* @return array|null
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_item_by_id($id): ?array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT id, name, price FROM items WHERE id = :id;");
|
||||
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
return $result ?: null;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving item by ID.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database without price info.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_no_price(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT items.name, cart.count
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving cart items without price.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all items in the cart from the database.
|
||||
*
|
||||
* @return array[]
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart(): array
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT
|
||||
cart.id,
|
||||
items.name,
|
||||
items.price,
|
||||
cart.count,
|
||||
ROUND(items.price * cart.count, 2) as total_price
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id
|
||||
ORDER BY cart.id;"
|
||||
);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error retrieving cart items.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total items count in cart
|
||||
*
|
||||
* @return int
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_count(): int
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query("SELECT COALESCE(SUM(count), 0) FROM cart;");
|
||||
return (int)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error getting cart count.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total price of all items in cart
|
||||
*
|
||||
* @return float
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_total(): float
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->query(
|
||||
"SELECT COALESCE(SUM(items.price * cart.count), 0.0)
|
||||
FROM cart
|
||||
INNER JOIN items ON cart.id = items.id;"
|
||||
);
|
||||
return (float)$stmt->fetchColumn();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error calculating cart total.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the cart or update its quantity.
|
||||
*
|
||||
* @param int $id
|
||||
* @param int $count
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error or item doesn't exist.
|
||||
*/
|
||||
public function add_to_cart($id, $count): bool
|
||||
{
|
||||
try {
|
||||
// Check if the item exists
|
||||
$item = $this->get_item_by_id($id);
|
||||
if (!$item) {
|
||||
throw new DbException("Item with ID $id does not exist.");
|
||||
}
|
||||
|
||||
// If count is 0 or less, remove the item from the cart
|
||||
if ($count <= 0) {
|
||||
return $this->remove_from_cart($id);
|
||||
}
|
||||
|
||||
// Insert or update the cart item
|
||||
$stmt = $this->pdo->prepare(
|
||||
"INSERT INTO cart (id, count)
|
||||
VALUES (:id, :count)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
count = excluded.count;"
|
||||
);
|
||||
return $stmt->execute(['id' => $id, 'count' => $count]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error adding/updating item in the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the cart
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function empty_cart(): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart");
|
||||
return $stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cart
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return bool
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function remove_from_cart($id): bool
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("DELETE FROM cart WHERE id = :id");
|
||||
return $stmt->execute(['id' => $id]);
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error removing item from the cart.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item quantity in cart
|
||||
*
|
||||
* @param int $id
|
||||
* @return int The quantity of the item in the cart, or 0 if not found.
|
||||
* @throws DbException If there's a database error.
|
||||
*/
|
||||
public function get_cart_item_quantity(int $id): int
|
||||
{
|
||||
try {
|
||||
$stmt = $this->pdo->prepare("SELECT count FROM cart WHERE id = :id");
|
||||
$stmt->execute(['id' => $id]);
|
||||
$result = $stmt->fetchColumn();
|
||||
return $result !== false ? (int)$result : 0;
|
||||
} catch (PDOException $e) {
|
||||
throw new DbException("Error getting cart item quantity.\nCaused by: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
5
semester-4/СмП/lb-4/src/templates/components/error.php
Normal file
5
semester-4/СмП/lb-4/src/templates/components/error.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php if (isset($error) && !empty($error)): ?>
|
||||
<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffebee;">
|
||||
<?php echo htmlspecialchars($error); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
5
semester-4/СмП/lb-4/src/templates/components/footer.php
Normal file
5
semester-4/СмП/lb-4/src/templates/components/footer.php
Normal file
@ -0,0 +1,5 @@
|
||||
<footer>
|
||||
<?php $cart_count = $cart_count ?? 0;
|
||||
include 'navigation.php'; ?>
|
||||
<p>© <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
|
||||
</footer>
|
7
semester-4/СмП/lb-4/src/templates/components/header.php
Normal file
7
semester-4/СмП/lb-4/src/templates/components/header.php
Normal file
@ -0,0 +1,7 @@
|
||||
<header>
|
||||
<h1>Продовольчий магазин "Весна"</h1>
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<h3>Добрий день <?php echo htmlspecialchars($_SESSION['user']['name']); ?></h3>
|
||||
<?php endif; ?>
|
||||
<?php include 'navigation.php'; ?>
|
||||
</header>
|
17
semester-4/СмП/lb-4/src/templates/components/navigation.php
Normal file
17
semester-4/СмП/lb-4/src/templates/components/navigation.php
Normal file
@ -0,0 +1,17 @@
|
||||
<nav>
|
||||
<?php if (isset($_SESSION['user'])): ?>
|
||||
<a href="?page=home">Головна</a>
|
||||
|
|
||||
<a href="?page=items">Товари</a>
|
||||
|
|
||||
<a href="?page=cart">Кошик (<?php echo $cart_count ?? 0; ?>)</a>
|
||||
|
|
||||
<a href="?page=profile">Профіль</a>
|
||||
|
|
||||
<a href="?page=logout">Вихід</a>
|
||||
<?php else: ?>
|
||||
<a href="?page=login">Вхід</a>
|
||||
|
|
||||
<a href="?page=register">Реєстрація</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
20
semester-4/СмП/lb-4/src/templates/layout.php
Normal file
20
semester-4/СмП/lb-4/src/templates/layout.php
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?php echo htmlspecialchars($title ?? 'Продовольчий магазин "Весна"'); ?></title>
|
||||
<link rel="stylesheet" href="public/css/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php include 'components/header.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<?php echo $content; ?>
|
||||
</div>
|
||||
|
||||
<?php include 'components/footer.php'; ?>
|
||||
</body>
|
||||
|
||||
</html>
|
9
semester-4/СмП/lb-4/src/templates/pages/404.php
Normal file
9
semester-4/СмП/lb-4/src/templates/pages/404.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: space-between;">
|
||||
<h1 style="text-align: center;">Будь-ласка увійдіть в акаунт для доступу до цієї сторінки</h1>
|
||||
<img src="public/assets/logo.png" alt="logo" style="width: 90%;">
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
46
semester-4/СмП/lb-4/src/templates/pages/cart.php
Normal file
46
semester-4/СмП/lb-4/src/templates/pages/cart.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php ob_start(); ?>
|
||||
<?php if (empty($cart_items)): ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик порожній <a href="?page=items">Перейти до покупок</a></h3>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div style="display: flex; align-items: center; justify-content: space-evenly;">
|
||||
<h3>Ваш кошик</h3>
|
||||
<h3 class="cart-summary">
|
||||
Загальна сума: <?php echo number_format($cart_total, 2); ?> грн
|
||||
</h3>
|
||||
|
||||
<button type="submit">Сплатити</button>
|
||||
|
||||
<form action="?page=cart&action=clear" method="POST" style="display: inline;">
|
||||
<button type="submit">Очистити</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="product-list">
|
||||
<?php foreach ($cart_items as $item): ?>
|
||||
<div>
|
||||
<h2>
|
||||
<?php echo htmlspecialchars($item['name']); ?>
|
||||
<br>
|
||||
<?php echo htmlspecialchars($item['count']); ?> шт.
|
||||
</h2>
|
||||
|
||||
<span>Ціна за одиницю: <?php echo number_format($item['price'], 2); ?> грн</span>
|
||||
<br>
|
||||
<span>Загальна ціна: <?php echo number_format($item['total_price'], 2); ?> грн</span>
|
||||
|
||||
<br><br>
|
||||
|
||||
<form action="?page=cart&action=remove" method="POST">
|
||||
<input type="hidden" name="product_id" value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
<button type="submit" style="width: 100%;">Видалити</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
8
semester-4/СмП/lb-4/src/templates/pages/home.php
Normal file
8
semester-4/СмП/lb-4/src/templates/pages/home.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="display: flex; flex-direction: column; align-items: center;">
|
||||
<img src="public/assets/logo.png" alt="logo" style="width: 90%;">
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
21
semester-4/СмП/lb-4/src/templates/pages/items.php
Normal file
21
semester-4/СмП/lb-4/src/templates/pages/items.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php ob_start(); ?>
|
||||
<h2>Доступні товари</h2>
|
||||
<div class="product-list">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div>
|
||||
<h2><?php echo htmlspecialchars($item['name']); ?></h2>
|
||||
<h3>Ціна: <?php echo number_format($item['price'], 2); ?> грн</h3>
|
||||
|
||||
<form action="?page=cart&action=add" method="POST">
|
||||
<input type="hidden" name="product_id" value="<?php echo htmlspecialchars($item['id']); ?>">
|
||||
<label for="quantity_<?php echo htmlspecialchars($item['id']); ?>">Кількість:</label>
|
||||
<input type="number" id="quantity_<?php echo htmlspecialchars($item['id']); ?>" name="quantity" value="0" min="0" max="100">
|
||||
<button type="submit">Купити</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
28
semester-4/СмП/lb-4/src/templates/pages/login.php
Normal file
28
semester-4/СмП/lb-4/src/templates/pages/login.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="max-width: 400px; margin: 0 auto;">
|
||||
<h2>Вхід в систему</h2>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<form method="POST" action="?page=login">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="username">Ім'я користувача:</label><br>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="password">Пароль:</label><br>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" style="width: 100%;">Увійти</button>
|
||||
</form>
|
||||
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a href="?page=register">Немає акаунта? Зареєструватися</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
32
semester-4/СмП/lb-4/src/templates/pages/profile.php
Normal file
32
semester-4/СмП/lb-4/src/templates/pages/profile.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php ob_start(); ?>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<div style="display: flex; flex-direction: column; align-items: center; justify-content: space-between;">
|
||||
<div style="width: 150px; height: 150px; border: 1px solid black; display: flex; justify-content: center; align-items: center; margin-bottom: 20px;">
|
||||
<img src="<?= htmlspecialchars($_SESSION['user']['photo_path']) ?>" alt="Profile Image" style="max-width: 100%; max-height: 100%; object-fit: contain;">
|
||||
</div>
|
||||
|
||||
<form style="display: grid; gap: 15px;" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="photo" accept="image/*">
|
||||
|
||||
<label style="font-weight: bold;">Ім'я</label>
|
||||
<input type="text" value="<?php echo $_SESSION['user']['name'] ?>" name="name">
|
||||
|
||||
<label style="font-weight: bold;">Фамілія</label>
|
||||
<input type="text" value="<?php echo $_SESSION['user']['surname'] ?>" name="surname">
|
||||
|
||||
<label style="font-weight: bold;">Вік</label>
|
||||
<input type="number" min="16" max="150" value="<?php echo $_SESSION['user']['age'] ?>" name="age">
|
||||
|
||||
<label style="font-weight: bold;">Про себе</label>
|
||||
<textarea style="height: 50px; resize: vertical;" name="description"><?php echo $_SESSION['user']['description'] ?></textarea>
|
||||
|
||||
<button>Сохранить</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
43
semester-4/СмП/lb-4/src/templates/pages/register.php
Normal file
43
semester-4/СмП/lb-4/src/templates/pages/register.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php ob_start(); ?>
|
||||
<div style="max-width: 400px; margin: 0 auto;">
|
||||
<h2>Реєстрація</h2>
|
||||
|
||||
<?php require 'templates/components/error.php' ?>
|
||||
|
||||
<form method="POST" action="?page=register">
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="username">Логін *:</label><br>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="password">Пароль * (мінімум 6 символів):</label><br>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="name">Ім'я *:</label><br>
|
||||
<input type="text" name="name" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="surname">Прізвище *:</label><br>
|
||||
<input type="text" name="surname" required>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 15px;">
|
||||
<label for="age">Вік *:</label><br>
|
||||
<input type="number" name="age" min="16" max="150" style="width: 97%;" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" style="width: 100%;">Зареєструватися</button>
|
||||
</form>
|
||||
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a href="?page=login">Вже є акаунт? Увійти</a>
|
||||
</p>
|
||||
</div>
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
include 'templates/layout.php';
|
||||
?>
|
Binary file not shown.
Reference in New Issue
Block a user