1
0

SMP all works

This commit is contained in:
Sytnyk Yehor
2025-06-05 12:21:37 +03:00
parent bdcbdb850a
commit d22b7ddcaf
34 changed files with 4932 additions and 0 deletions

View File

@ -0,0 +1,4 @@
> [!NOTE]
> Викладач: Сокорчук І. П.
>
> Оцінка: 100

View 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();

View 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();

View 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();
```

View File

@ -0,0 +1,4 @@
> [!NOTE]
> Викладач: Сокорчук І. П.
>
> Оцінка: 80

View 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();

View 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>&copy; <?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>&copy; <?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>&copy; <?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 }

View 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>&copy; <?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>&copy; <?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>&copy; <?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());
}
}
}
```

View File

@ -0,0 +1,4 @@
> [!NOTE]
> Викладач: Сокорчук І. П.
>
> Оцінка: In Progress

View 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';
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View 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;
}

View 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';
}
}

View 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';
}
}

View 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';
}
}

View 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';
}
}

View File

@ -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';
}
}

View 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);
}
}

View 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());
}
}
}

View 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; ?>

View File

@ -0,0 +1,5 @@
<footer>
<?php $cart_count = $cart_count ?? 0;
include 'navigation.php'; ?>
<p>&copy; <?php echo date("Y"); ?> ТОВ "Весна". Усі права захищені.</p>
</footer>

View 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>

View 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>

View 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>

View 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';
?>

View 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';
?>

View 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';
?>

View 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';
?>

View 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';
?>

View 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';
?>

View 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';
?>