Please use this identifier to cite or link to this item: https://er.chdtu.edu.ua/handle/ChSTU/6882
Title: Сайт компанії з надання послуг краси та здоров'я
Authors: Рудницький , Сергій Володимирович
Декен, Євгенія Романівна
Keywords: веб-сайт салону краси;функціональні модулі;Symfony;проектування інформаційної архітектури;тестування веб-сайтів;онлайн-бронювання;персоналізація користувацького досвіду
Issue Date: 11-Jun-2024
Abstract: Актуальність кваліфікаційної роботи полягає у необхідності розробки сучасних веб-сайтів для салонів краси, що відповідають високим стандартам дизайну, функціональності та зручності взаємодії для користувачів. Метою роботи є створення ефективного веб-ресурсу для салону краси "Golden Epil", який би інтегрував сучасні технологічні рішення та відповідав б потребам цільової аудиторії. Об'єктом дослідження є процес обслуговування клієнтів у салоні, а предметом — веб-сайт компанії, що надає послуги краси та здоров'я. Методами дослідження виступають аналіз конкуруючих веб-сайтів, проектування інформаційної архітектури, програмування на Symfony та тесту вання користувацького інтерфейсу. Робота має велику практичну значимість, оскільки результати дослідження дозволили створити веб-сайт, який сприяє ефективнішому просуванню послуг салону в інтернеті, підвищенню лояльності клієнтів та оптимізації процесів обслуговування. Кваліфікаційна робота бакалавра складається з трьох основних розділів. Перший розділ присвячений аналізу ринку веб-сайтів для салонів краси та ідентифікації ключових трендів і недоліків існуючих рішень. Другий розділ описує процес проектування інформаційної архітектури та дизайну веб-сайту, а також реалізацію функціональних модулів з використанням фреймворку Symfony. Третій розділ зосереджується на тестуванні системи, оцінці її ефективності та впровадженні сайту у діяльність салону.
URI: https://er.chdtu.edu.ua/handle/ChSTU/6882
Appears in Collections:126 Інформаційні системи та технології (Web-технології, web-дизайн)

Files in This Item:
File Description SizeFormat 
РЕП_БАК__Декен_WEBС-2211.pdf
  Restricted Access
3.62 MBAdobe PDFView/Open Request a copy


Items in DSpace are protected by copyright, with all rights reserved, unless otherwise indicated.

Extracted text
 
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ 
ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ ФАКУЛЬТЕТ ІНФОРМАЦІЙНИХ 
ТЕХНОЛОГІЙ І СИСТЕМ  
КАФЕДРА ІНФОРМАЦІЙНИХ ТЕХНОЛОГІЙ ПРОЕКТУВАННЯ  
  
  
  
  
  
ПОЯСНЮВАЛЬНА ЗАПИСКА  
до кваліфікаційної роботи бакалавра  
    
на тему: “Сайт компанії з надання послуг краси та здоров'я” 
  
  
  
  
  
Виконала: здобувачка першого  
(бакалаврського) рівня вищої освіти  
  2    курсу, групи WEBC-2211 
Спеціальності 126 Інформаційні системи та 
технології  
ОП «Web-технології, Web-дизайн»  
 
       Декен Євгенія Романівна               ф          
 
Керівник: к.т.н, доцент, Рудницький Сергій 
Володимирович 
  
Рецензент: : к.т.н, доцент,  Дідук Віталій 
Андрійович  
  
  
  
  
 Черкаси – 2024 року   
` 
 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
(назва вузу) 
 
Факультет ФІТІС       Кафедра Інформаційних технологій проектування                                               
Освітній рівень                      бакалавр 
Спеціальність                        126 “Інформаційні системи та технології” 
Освітня програма                  “Web-технології Web-дизайн” 
                                                                                           ЗАТВЕРДЖУЮ: 
                                                                    зав.Кафедри Прокопенко Т.О. 
                                                                    “______” __________________2024р. 
ЗАВДАННЯ 
На кваліфікаційну роботу студенту 
 
Декен Євгенія Романівна 
(прізвище, ім’я, по-батькові) 
1. Тема проекту (роботи) Сайт компанії з надання послуг краси та здоров'я                                                                                            
Керівник проекту(роботи) РУДНИЦЬКИЙ С.В., к.т.н., доцент.                                                
СЗаттрвеоркд жпеондаа ннанкаяз осмт Чудерекнатсьокмог ро одберожав
від 26.02.2024 ти  ного технологічного університету №60/40  
29 травня2024 р                                                              
3. Вихідні дані до проекту (роботи) Середовище розробки PhpStorm; XAMPP; Мова 
програмування – PhP, веб-інтерфейс для адміністрування баз даних MySql – PhpMyAdmin. 
4. Зміст розрахунково-пояснювальної записки (перелік питань, що їх належить 
розробити) 
ВСТУП, РОЗДІЛ 1: ПОСТАНОВКА ЗАДАЧІ. ОГЛЯД ІСНУЮЧИХ РІШЕНЬ АНАЛОГІВ, 1.1 
Розмір ринку та конкурентне середовище, 1.3 Тенденції розвитку ринку, 1.4 Аналіз існуючих 
продуктів, 1.5 Аналіз можливостей застосування аналогів для вирішення поставленої задачі, 1.6 
Виявлення потреби у покращенні систем, Висновки до розділу 1, РОЗДІЛ 2: ВИБІР ЗАСОБІВ 
РОЗРОБКИ ТА     ПРОЕКТУВАННЯ САЙТУ, 2.1 Призначення розроблюваної інформаційної 
системи, 2.2 Опис завдань, які передбачається вирішити за допомогою системи, 2.3 Визначення 
цільової аудиторії та їх потреб, 2.4 Формування вимог, 2.5 Конструювання дизайну сайту, 2.6 
Вибір засобів розробки та ПЗ, Висновки до розділу 2, РОЗДІЛ 3: РОЗРОБКА СИСТЕМИ, 3.1 
Підготовка середовища розробки, 3.2 Робота з БД, формування зав’язків та таблиць, 3.3 
Використання Symfony Sonata для створення адмінки, 3.4 Створення дизайну сайту та його 
архітектури сторінок, 3.5 Створення шаблонів та розробка контролерів сторінок, 3.6 Створення 
сервісів сторінок, 3.7 Створення логіну та регістрації на сайті, 3.8 Symfony Security, Висновки 
до розділу 3, ВИСНОВКИ, ВИКОРИСТАНІ ДЖЕРЕЛА, ДОДАТОК А, ДОДАТОК Б, ДОДАТОК 
В 
5.Перелік графічного матеріалу (з точним зазначенням обов’язкових креслень): 
1. Рисунок: Схема бази даних 
2. Рисунок: Макет сайту 
3. Рисунок: Архітектура сайту 
     ЧДТУ 242280.003 ПЗ Лист 
     
3 
Зм Арк. № докум. Пiдпис. Дата 
нт
 
6. Консультанти з проекту із зазначенням розділів проекту, що їх стосуються 
                 Підпис, дата 
Розділ Консультант 
           Завдання видав, завдання 
                       прийняв 
 
    
 
    
 
 
7. Дата видачі завдання         1 березня 2024 р.                                                           
Керівник   Рудницький С.В.                                        
(підпис) 
                                                 Завдання прийняв до виконання   ________ 
                                                                                                     (підпис) 
Календарний план 
Пор. № Назва етапів дипломного проекту Термін виконання Примітка 
етапів проекту 
 Підготовча стадія   
 Постановка задачі  Виконано 
 Підготовка завдання  Виконано 
 Погодження завдання  Виконано 
 Затвердження завдання  Виконано 
 Основна стадія   
 Підбір матеріалів  Виконано 
 Аналіз шляхів рішення задачі  Виконано 
 Розрахунок основних параметрів роботи  Виконано 
 Вибір кінцевого варіанту проектного рішення  Виконано 
 Оформлення первісної редакції роботи  Виконано 
 Заключна стадія   
 Узгодження проектних рішень з керівником  Виконано 
 Оформлення пояснювальної записки  Виконано 
 Попередній захист роботи  Виконано 
 Затвердження роботи  Виконано 
 Рецензування роботи  Виконано 
 Захист роботи   
Студент-дипломник                                              Декен Євгенія Романівна 
(підпис)                                               (ПІБ) 
Керівник проекту          _____________________________________________
 
АНОТАЦІЯ 
 
 
У роботі представлено 66 сторінок тексту, 38 ілюстрація, 1 таблиця, 3 додатки 
та використано 14 джерела літератури. 
Актуальність кваліфікаційної роботи полягає у необхідності розробки 
сучасних веб-сайтів для салонів краси, що відповідають високим стандартам 
дизайну, функціональності та зручності взаємодії для користувачів. Метою роботи 
є створення ефективного веб-ресурсу для салону краси "Golden Epil", який би 
інтегрував сучасні технологічні рішення та відповідав б потребам цільової 
аудиторії. Об'єктом дослідження є процес обслуговування клієнтів у салоні, а 
предметом — веб-сайт компанії, що надає послуги краси та здоров'я. 
Методами дослідження виступають аналіз конкуруючих веб-сайтів, 
проектування інформаційної архітектури, програмування на Symfony та тесту 
вання користувацького інтерфейсу. Робота має велику практичну значимість, 
оскільки результати дослідження дозволили створити веб-сайт, який сприяє 
ефективнішому просуванню послуг салону в інтернеті, підвищенню лояльності 
клієнтів та оптимізації процесів обслуговування. 
Кваліфікаційна робота бакалавра складається з трьох основних розділів. 
Перший розділ присвячений аналізу ринку веб-сайтів для салонів краси та 
ідентифікації ключових трендів і недоліків існуючих рішень. Другий розділ описує 
процес проектування інформаційної архітектури та дизайну веб-сайту, а також 
реалізацію функціональних модулів з використанням фреймворку Symfony. Третій 
розділ зосереджується на тестуванні системи, оцінці її ефективності та 
впровадженні сайту у діяльність салону. 
Ключові слова: веб-сайт салону краси, Symfony, проектування інформаційної 
архітектури, функціональні модулі, тестування веб-сайтів, онлайн-бронювання, 
персоналізація користувацького досвіду. 
 
ANNOTATION OF THE THESIS 
 
 
The thesis comprises 66 pages of text, 38 illustrations, 1 tables, 3 appendices, and 
utilizes 14 literary sources. 
The relevance of this thesis is underscored by the necessity for developing modern 
websites for beauty salons that meet high standards in design, functionality, and user 
interaction convenience. The aim of the thesis is to create an effective web resource for 
the "Golden Epil" beauty salon, which would integrate modern technological solutions 
and meet the needs of its target audience. The object of research is the customer service 
process at the salon, and the subject is the company’s website that offers beauty and health 
services. 
The research methods include analyzing competing websites, designing 
information architecture, programming with Symfony, and testing the user interface. The 
work has significant practical importance as the research results facilitated the creation 
of a website that enhances the online promotion of the salon's services, increases customer 
loyalty, and optimizes service processes. 
The thesis consists of three main sections. The first section is dedicated to 
analyzing the market of websites for beauty salons and identifying key trends and 
shortcomings of existing solutions. The second section describes the process of designing 
the information architecture and website design, as well as the implementation of 
functional modules using the Symfony framework. The third section focuses on testing 
the system, evaluating its effectiveness, and implementing the site into the salon’s 
operations. 
Keywords: beauty salon website, Symfony, information architecture design, 
functional modules, website testing, online booking, user experience personalization. 
 
 
ЗМІСТ 
 
СПИСОК СКОРОЧЕНЬ ТА УМОВНИХ ПОЗНАЧЕНЬ ..................................... 4 
ВСТУП ...................................................................................................................... 5 
РОЗДІЛ 1 ПОСТАНОВКА ЗАДАЧІ. ОГЛЯД ІСНУЮЧИХ РІШЕНЬ, ТА 
АНАЛОГІВ .............................................................................................................. 8 
1.1 Розмір ринку та конкурентне середовище ...................................................... 8 
1.2 Тенденції розвитку ринку ................................................................................. 9 
1.3 Аналіз існуючих продуктів ............................................................................ 11 
1.4 Аналіз можливостей застосування аналогів для вирішення поставленої  
задачі ....................................................................................................................... 19 
1.5 Виявлення потреби у покращенні систем ..................................................... 20 
Висновки до розділу 1: ......................................................................................... 21 
РОЗДІЛ 2 ВИБІР ЗАСОБІВ РОЗРОБКИ ТА ПРОЕКТУВАННЯ САЙТУ ...... 22 
2.1 Призначення розроблюваної інформаційної системи. ................................ 22 
2.2 Опис завдань, які передбачається вирішити за допомогою системи. ....... 23 
2.3 Визначення цільової аудиторії та їх потреб. ................................................ 24 
2.4 Формування вимог .......................................................................................... 25 
2.5 Конструювання дизайну сайту ...................................................................... 25 
2.6 Вибір засобів розробки та ПЗ ......................................................................... 28 
Висновки до розділу 2: ......................................................................................... 34 
РОЗДІЛ 3  РОЗРОБКА СИСТЕМИ ..................................................................... 35 
3.1 Підготовка середовища розробки: ................................................................. 35 
3.2 Робота з БД, формування зав’язків та таблиць ............................................ 40 
3.3 Використання Symfony Sonata для створення адмінки ............................... 43 
3.4 Створення дизайну сайту та його архітектури сторінок ............................. 48 
3.5 Створення шаблонів та розробка контролерів сторінок ............................. 52 
 
ЧДТУ 242280.003 ПЗ 
Зм. Лист № докумемента Підпис Дата  
Розроб. Декен Є.Р.   Літ. Лист Листів 
Пе рев. Р удницьки Сайт компанії з надання послуг Н  2   66 
Рец енз.. ДідйуСк .ВВ.А краси та здоров'я ФІ ТІС, 
Н.к онтр.  Р удницьки  
 Пояснювальна записка 
Затв. Прокопйе нСк.ВоТ .О кафедра ІТП, WEBC-2211 
    
 .  
 
 
3.6 Створення сервісів сторінок ........................................................................... 57 
3.7 Створення логіну та реєстрації на сайті ....................................................... 59 
3.8 Symfony Security .............................................................................................. 63 
Висновки до розділу 3: ......................................................................................... 67 
ВИСНОВКИ ........................................................................................................... 68 
ВИКОРИСТАНІ ДЖЕРЕЛА ................................................................................ 70 
ДОДАТОК А .......................................................................................................... 73 
ДОДАТОК Б .......................................................................................................... 75 
ДОДАТОК В ........................................................................................................ 117 
 
 
  
 
ЧДТУ 242280.003 ПЗ 
Зм. Лист № докумемента Підпис Дата  
Розроб. Декен Є.Р.   Літ. Лист Листів 
П ерев. Р удницький Сайт компанії з надання послуг краси  Н  3  66 
Ре ценз.. ДідуСк. ВВ.А та здоров'я Ф ІТІС, 
Н.к онтр.  Рудницький  
 Пояснювальна записка 
З атв. кафедра ІТП, WEBC-2211 
 Прок опеСнк.Во Т.О. 
 
   
` 
 
СПИСОК СКОРОЧЕНЬ ТА УМОВНИХ ПОЗНАЧЕНЬ 
 
 
 PHP – Скриптова мова програмування на стороні вебсерверу. 
 URL – Унікальна адреса ресурсу. 
 MySQL – Система керування реляційними базами даних. 
 SQL -  Мова запросів до бази данних. 
 PhpMyAdmin – Веб-інтерфейс для адміністрування баз даних MySQL. 
 ПЗ – Програмне забезпечення. 
 БД – База даних 
 HTML – Мова розмітки документів.  
     ЧДТУ 242280.003 ПЗ Лист 
      
4 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
ВСТУП 
 
У сучасному світі веб-технології відіграють ключову роль у просуванні 
бізнесу та підвищенні його конкурентоспроможності. З огляду на це, 
актуальність розробки ефективної веб-орієнтованої інформаційної системи для 
салонів краси, особливо у регіоні Черкаси, є важливою та своєчасною задачею. 
Аналіз існуючих аналогів показує, що багато веб-сайтів не відповідають 
сучасним вимогам користувачів щодо зручності, функціональності та дизайну, 
що веде до втрати потенційних клієнтів. Тому, створення добре продуманої, 
адаптивної та функціональної веб-платформи може значно вплинути на 
успішність бізнесу у цій галузі. 
Додатково, з точки зору маркетингу, наявність професійного сайту сприяє 
підвищенню впізнаваності бренду і довіри серед клієнтів. В умовах зростаючої 
конкуренції, особливо в маленьких та середніх містах, як Черкаси, наявність 
зручного та сучасного сайту може стати вирішальним чинником для вибору 
місця надання послуг краси. Важливо відмітити, що такий веб-ресурс має бути 
не лише інформативним, але й естетично привабливим, оскільки саме візуальний 
вигляд часто є першим, на що звертає увагу потенційний клієнт. 
Наразі багато веб-сайтів у галузі краси зосереджуються лише на базовій 
функціональності, такій як галерея зображень та контактна інформація, проте не 
використовують можливості сучасних веб-технологій для створення 
інтерактивних функцій, таких як онлайн-запис, персоналізовані рекомендації або 
інтеграція з соціальними медіа. Це створює потенціал для розвитку і покращення 
сервісу, що може значно вплинути на залучення нових клієнтів та збільшення 
лояльності існуючих. 
Мета роботи полягає в розробці веб-ресурсу компанії з надання послуг 
краси та здоров'я з використанням сучасних технологій створення WEB-
додатків. 
 
 
     ЧДТУ 242280.003 ПЗ Лист 
      
5 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Для досягнення поставленої мети треба виконати наступні завдання: 
1. Проаналізувати існуючі рішення: Дослідити ринок веб-сайтів для 
салонів краси, визначити їхні ключові недоліки та переваги, щоб врахувати це 
при проектуванні. 
2. Спроєктувати архітектуру та дизайн: Розробити інформаційну 
архітектуру сайту та створити дизайн, який буде відповідати сучасним 
тенденціям та естетичним вимогам цільової аудиторії. 
3. Реалізувати функціонал сайту: Програмування веб-додатку з 
функціональними можливостями для кабінетів майстрів та користувачів, 
включаючи систему запису на процедури та курси. 
4. Створити кабінети користувача та майстра 
5. Створити логін та реєстрацію 
6. Написати телеграм бот 
7. Забезпечити безпеку 
8. Створити базу та підключення до неї  
9. Реалізувати можливість запису на процедуру та правки запису 
10. Протестувати веб-платформу: Провести тестування всіх 
компонентів сайту для забезпечення їхньої надійності, безпеки та зручності 
користування. 
11. Обґрунтувати економічну доцільність: Аналіз економічної 
ефективності впровадження веб-платформи, оцінка потенційного зростання 
доходів салону від оптимізації управління та залучення нових клієнтів. 
Об’єкт дослідження: Процес надання послуг у компанії з надання послуг 
краси та здоров'я. Цей процес включає всі аспекти взаємодії з клієнтами, від 
запису до виконання послуг, і є основою для розробки ефективної інформаційної 
системи, спрямованої на покращення сервісу та управління, створення сайту 
який є сучасним інструментом для залучення клієнтів та підвищення якості 
обслуговування. Сайт спроектовано з метою надання детальної інформації про 
послуги, майстрів та спеціальні пропозиції, які пропонує салон.  
     ЧДТУ 242280.003 ПЗ Лист 
      
6 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Предмет дослідження: Сайт компанії з надання послуг краси та здоров'я, 
що забезпечує онлайн-запис і інформування клієнтів про послуги та спеціальні 
пропозиції. Сайт є інструментом для реалізації стратегії обслуговування клієнтів, 
що дозволяє залучати нових клієнтів та підтримувати високий рівень 
задоволення постійних клієнтів.  
Методи дослідження включають аналіз вимог, проєктування архітектури 
системи, програмування на PHP і JavaScript з використанням Symfony, 
тестування. 
Практичне значення полягає у поліпшенні якості обслуговування 
клієнтів і збільшенні прибутковості підприємства завдяки впровадженню 
ефективного веб-сайту який забезпечить зручне та ефективне взаємодіяння з 
клієнтами, поліпшення якості обслуговування та підвищення загальної 
ефективності бізнесу. Для реалізації цих цілей буде використано фреймворк 
Symfony, який дозволяє створювати надійні та масштабовані веб-додатки. 
Висновки щодо результатів впровадження будуть підкріплені фактичними 
даними про збільшення клієнтської бази і прибутків салону. 
Структура роботи охоплює вступ, три основних розділи, висновки, 
список використаних джерел та додатки. Загальний обсяг роботи складає 
близько 66 сторінок, включаючи 1 таблицю і 38 рисунків, заснованих на 14 
джерелах. 
 
 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
7 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
РОЗДІЛ 1 
ПОСТАНОВКА ЗАДАЧІ. ОГЛЯД ІСНУЮЧИХ РІШЕНЬ, 
ТА АНАЛОГІВ 
 
 
1.1 Розмір ринку та конкурентне середовище  
 
Ринок сайтів салонів краси в Україні активно розвивається, підтримуваний 
глобальними тенденціями у сфері б'юті-індустрії. За даними Trafft, однією з 
ключових тенденцій у 2023 році є розширення послуг, що включає впровадження 
нових технологій та послуг, таких як онлайн-консультації та персоналізація 
обслуговування, що стає важливим чинником вибору салону для клієнтів. Ці 
аспекти ведуть до збільшення довіри і задоволеності клієнтів, що в свою чергу 
сприяє збільшенню відвідуваності сайтів салонів краси. [1] 
У контексті глобального ринку, за даними Grand View Research, розмір 
світового ринку салонів краси у 2022 році оцінювався у 155.60 мільярдів доларів, 
і прогнозується що він зростатиме з річним темпом 8% до 2030 року. Це 
зростання стимулюється поширенням інноваційних процедур краси та акцентом 
на підвищення загального досвіду клієнтів у салонах [2]. 
Ці тенденції свідчать про зростаючий інтерес і потреби споживачів у 
використанні онлайн-ресурсів для замовлення послуг салонів краси, що робить 
розробку високоякісного веб-сайту для салону краси не лише актуальною, але й 
стратегічно важливою для підвищення конкурентоспроможності на ринку. 
     ЧДТУ 242280.003 ПЗ Лист 
      
8 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
Рисунок 1.1 Тенденції салонів краси за Grand View Research 
 
Ринок сайтів салонів краси характеризується високим рівнем конкуренції. 
Серед основних конкурентів можна виділити: 
 Локальні сайти салонів краси: Ці сайти зазвичай мають 
обмежений бюджет на маркетинг, але вони добре оптимізовані для локального 
пошуку, що робить їх видимими для користувачів, які шукають послуги в 
конкретному районі. 
 Національні сайти салонів краси: Маючи більший бюджет на 
маркетинг, вони охоплюють ширшу аудиторію та пропонують більш широкий 
спектр послуг. 
 Сайти-агрегатори: Ці платформи дають користувачам можливість 
порівняти послуги та ціни різних салонів краси, що робить їх зручним 
інструментом для вибору оптимального варіанту. 
 
1.2 Тенденції розвитку ринку  
 
У сучасному цифровому світі, де конкуренція серед бізнесів стає все 
гострішою, значення ефективної онлайн присутності не можна недооцінювати. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
9 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 Особливо це стосується сфери послуг, зокрема салонів краси, де перше 
враження може стати вирішальним у виборі клієнтом саме вашого закладу. 
Ринок веб-сайтів салонів краси в Україні демонструє вражаючі темпи росту та 
адаптації до новітніх технологічних трендів, що є свідченням важливості таких 
платформ для бізнесу. 
1. Зростання онлайн-бронювання: Ця тенденція підтверджується 
збільшенням кількості користувачів, які воліють здійснювати бронювання через 
інтернет, цим зберігаючи свій час та забезпечуючи собі комфортний вибір 
послуг. Дослідження показують, що такі платформи не лише зручні, але й 
дозволяють клієнтам ретельніше планувати свій візит. 
2. Персоналізація послуг: Салони краси, що інтегрують 
персоналізацію в свої веб-сайти, часто виявляються на крок попереду своїх 
конкурентів. Це не лише збільшує лояльність клієнтів, але й значно покращує 
їхній досвід, адаптуючи послуги до особистих переваг клієнтів, що у свою чергу 
може спонукати їх повернутися. 
3. Інтеграція з соціальними мережами: Соціальні мережі 
залишаються потужним інструментом для маркетингу та залучення нових 
клієнтів. Інтеграція соціальних мереж із сайтами салонів краси не тільки 
спрощує процес спілкування з клієнтами, але й дозволяє власникам бізнесу 
швидко реагувати на запити та підтримувати активність аудиторії. 
Ці тренди підкреслюють не тільки зростання та адаптацію ринку, але й 
наголошують на важливості інвестування в якісні веб-ресурси для салонів краси. 
З огляду на ці тенденції, можна стверджувати, що інвестування в розвиток 
онлайн-платформ є необхідним для забезпечення успіху в сучасних умовах 
глобального ринку.  
  
     ЧДТУ 242280.003 ПЗ Лист 
      
10 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Враховуючи ці фактори, стає очевидним, що розробка привабливого, 
функціонального та взаємопов'язаного з соціальними мережами веб-сайту є 
критично важливою для підтримання конкурентоспроможності та залучення 
нових клієнтів. 
 
1.3 Аналіз існуючих продуктів 
 
Критерії аналізу сайта: 
При аналізі вебсайта, особливо у галузі салонів краси, важливо 
застосовувати всебічний набір критеріїв, який відображає суть того, що робить 
вебсайт ефективним і зручним для користувача. Ось детальніше про кожен 
критерій: 
1. Веб-дизайн: 
 Естетика: Візуальна привабливість сайту, включаючи колірну 
схему, типографіку та загальний макет. Як візуально привабливий сайт? Чи 
відповідає він іміджу бренду? 
 Зручність використання: Легкість навігації, інтуїтивність макету 
та доступність функцій. Чи легко новому користувачу орієнтуватися на сайті? Чи 
є функції доступності для користувачів з обмеженими можливостями? 
 Адаптивність: Здатність вебсайту ефективно функціонувати на 
різних пристроях і розмірах екранів, від настільних комп'ютерів до смартфонів. 
2. Можливості запису на процедуру: 
 Простота процесу бронювання: Наскільки легко користувачу 
забронювати прийом? Чи інтегрований інтерфейс бронювання в сайт або 
переадресовує на іншу платформу? 
 Різноманітність послуг: Чи дозволяє сайт бронювання широкого 
спектру послуг?  
 Система підтвердження та нагадувань: Чи надає сайт негайні 
підтвердження бронювання та нагадування через електронну пошту або SMS? 
3. Точність і глибина інформації: 
     ЧДТУ 242280.003 ПЗ Лист 
      
11 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 Деталі послуг: Наявність детальних описів кожної послуги, 
включаючи тривалість, ціну та що чекати під час процедури. 
 Інформація про компанію: Інформація про історію, філософію та 
команду салону. Чи надає сайт біографії персоналу? 
 Освітній контент: Чи пропонує сайт статті, блоги або відео, які 
надають додаткову цінність користувачу, такі як поради з краси або інформація 
про здоров'я? 
4. Взаємодія з клієнтами та підтримка: 
 **МожливостіКритерії оцінки веб-сайту, особливо у контексті 
салонів краси, є суттєвими для розуміння того, як саме сайт може впливати на 
взаємодію з клієнтами та загальний успіх бізнесу. Задля глибшого аналізу веб-
сайтів важливо розглянути низку критеріїв: 
5. Веб-дизайн: 
 Естетика та візуальна привабливість: Важливість атрактивного 
візуального оформлення, що відповідає бренду та приваблює відвідувачів. 
 Зручність користування: Легкість навігації по сайту, яка 
забезпечує швидкий доступ до необхідної інформації та послуг. 
 Адаптивність: Вміння сайту ефективно працювати на різних 
пристроях і розмірах екранів. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
12 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
6. Можливості запису на процедуру: 
 Простота бронювання: Чи забезпечений сайт інтуїтивно 
зрозумілою системою онлайн-бронювання. 
 Інформаційна насиченість: Наявність детальних описів послуг, які 
допомагають клієнтам у прийнятті рішень. 
 Система підтвердження та нагадувань: Чи надає сайт автоматичні 
підтвердження та нагадування про записи. 
7. Інформаційний зміст: 
 Доступність і точність інформації: Чи легко знайти важливу 
інформацію, таку як контактні дані, години роботи, список послуг. 
 Оновлення контенту: Частота оновлення інформації на сайті, що 
відображає актуальність послуг та спеціальних пропозицій. 
8. Взаємодія з клієнтами: 
 Інтеграція соціальних медіа: Наявність зв'язків з соціальними 
мережами для залучення більшої аудиторії та підтримки комунікації з клієнтами. 
 Контактна форма та підтримка: Легкість звернення до 
адміністрації сайту, швидкість та якість реакції на запити відвідувачів. 
Для проведення детального аналізу було вирішено обрати кілька 
найпопулярніших і найбільш рейтингових салонів краси. Також досліджувались 
їхні офіційні вебсайти, щоб отримати повне уявлення про надані послуги, цінову 
політику, відгуки клієнтів та загальний рівень обслуговування. 
Далі розглянемо сайти-аналоги для подальшого проведення порівняльного 
аналізу. 
 
Летуаль (letual.com.ua) 
 Цей веб-сайт ізящно поєднує в собі велику кількість анімацій та 
інтерактивних елементів, що створюють захоплююче враження для кожного 
відвідувача. Його дизайн приваблює та зачаровує: красиві кольори, які 
розфарбовують екран, ненав'язливо мандрівні айфони та привітні для ока 
шрифти - все це робить перебування на цьому сайті справжнім задоволенням. 
     ЧДТУ 242280.003 ПЗ Лист 
      
13 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Проте, серед його мінусів можна відзначити контактну форму, яка, хоч і є 
зручною у багатьох відношеннях, все ж змушує користувачів витрачати час на 
вибір послуг шляхом їх написання у формі. Це може бути не так зручно для тих, 
хто шукає швидку і безпосередню взаємодію. 
Однак, не дивлячись на це, сайт має чудово організовану навігацію: легко 
доступне дерево посилань та інтуїтивно зрозуміле меню роблять перехід від 
однієї сторінки до іншої швидким та приємним. 
 
Рисунок 1.2 Сайт Летуаль  
     ЧДТУ 242280.003 ПЗ Лист 
      
14 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
GYMMAXX(gymmaxx.com/beauty/): 
 Цей сайт також вражає багатством анімацій, але вони відрізняються 
від попередніх: тепер вони реагують на дії клієнта, що робить взаємодію ще 
більш захоплюючою та персоналізованою. Після кожного банера нам 
зустрічається форма, проте її призначення не завжди однозначне, що може 
призвести до певного незручності в користувачів, які шукають конкретні 
послуги. 
 Однак, сайт відрізняється приємною коричневою гамою, яка створює 
атмосферу затишку та довіри, а також має дружелюбний інтерфейс, 
орієнтований на потреби клієнтів, що робить перебування на ньому ще більш 
приємним та комфортним. 
 
Рисунок 1.3 Сайт GYMMAXX 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
15 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Sensa Salon (sensasalon.com) 
Сайт використовує зручну структуру та привабливий візуальний дизайн, 
що робить навігацію по ресурсу інтуїтивно зрозумілою. Значна увага 
приділяється деталям, таким як якість зображень та тексту, що підсилює загальну 
привабливість сайту. 
Весь сайт виглядає як гламурний журнал, що приємно просто переглянути, 
такий сайт надає можливість записатися на процедуру чи залишити питання в 
контактній формі. 
 
 
Рисунок 1.4 Сайт Sensa Salon 
 
Bad Apple Hair (badapplehair.com): 
Ця мережа салонів, що базується у Вест-Мідлендсі, пропонує широкий 
спектр послуг і проводить програму стажування. Веб-сайт відомий своїм 
сучасним, чистим дизайном, який покращує взаємодію з користувачем і 
полегшує бронювання зустрічей. 
     ЧДТУ 242280.003 ПЗ Лист 
      
16 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Інтерфейс дуже комфортний та зрозумілий, дизайн виглядає дорого та 
розкішно.  
 
Рисунок 1.4 Сайт Bad Apple Hair 
В таблиці 1.1 наведені результати порівняння розглянутих аналогів: 
 
Таблиця 1.1 Порівняння аналогів 
 
Критерій Летуаль GYMMAXX Sensa Salon Bad Apple Hair Golden Epil 
Адаптивний до Адаптивний до Адаптивний до різних 
різних пристроїв Адаптивний до Адаптивний до 
різних пристроїв пристроїв та розмірів 
Адаптивність різних пристроїв та 
розмірів екранів. та розмірів різних пристроїв та та розмірів екранів, змінює вигляд 
 екранів. розмірів екранів. екранів. меню заради комфорту 
  користувачів 
Telegram-bot Відсутній Відсутній Відсутній Відсутній Присутній, виконує 
   функції нагадування 
Зручний, з Зрозумілий запис с 
Зручний, але можливістю селектами та 
Простота користувачам Інтуїтивно календарем вибору 
потрібно вводити зрозумілий, з записатися на Зручний, з 
бронювання процедуру або онлайн системою дати, для запису 
-
 назву послуги онлайн-системою 
бронювання. залишити питання бронювання. необхідна реєстрація, 
 
вручну.  в контактній що захищає від не 
 
формі. бажаних та непотрібних 
 випадкових записів 
Різноманітність Широкий спектр Широкий спектр Широкий спектр Широкий спектр Широкий спектр 
послуг послуг. послуг. послуг. послуг. послуг. 
Надає негайні 
підтвердження Надає 
бронювання та Надає підтвердження Надає 
Система автоматичні автоматичні Має функцію 
підтвердження нагадування через підтвердження та бронювання та підтвердження та нагадування через 
та нагадувань електронну пошту можливість 
 або SMS. нагадування про отримувати нагадування про телеграм 
 записи. нагадування. записи. 
  
 
     ЧДТУ 242280.003 ПЗ Лист 
      
17 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Критерій Летуаль GYMMAXX Sensa Salon Bad Apple Hair Golden Epil 
Детальні описи Детальні описи Детальні описи Детальні описи 
кожної послуги, кожної послуги, кожної послуги, кожної послуги, Має опис послуг та 
Деталі послуг включаючи включаючи включаючи включаючи виводить вартість 
тривалість, ціну та тривалість, ціну тривалість, ціну та тривалість, ціну враховуючи доп 
очікування. та очікування. очікування. та очікування. матеріали для послуг 
   
Інформація про Інформація про Інформація про Інформація про Інформація про Має сторінку "Про нас" 
компанію історію, філософію історію та історію та команду історію та в якії описана команда 
 та команду салону. команду салону. салону. команду салону. та історія салону 
Статті, блоги та Присутній так я сайт 
Освітній контент відео про красу та Відсутній. Відсутній. Відсутній. надає курси по 
 
здоров'я. декільком 
 спеціальностям 
Наявний, з Наявний, з 
Зворотній можливістю можливістю Наявний, з можливістю 
зв'язок Відсутня. зв'язатися через залишити питання Відсутня. зв'язатися через 
 
соціальні мережі. в контактній соціальні мережі. 
 формі. 
Контактна 
форма та Зручна контактна Зручна контактна Зручна контактна Зручна контактна Зручна й зрозуміла 
підтримка форма. форма. форма. форма. контактна форма 
 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
18 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
1.4 Аналіз можливостей застосування аналогів для вирішення 
поставленої задачі 
 
На основі детального аналізу аналогів можна сформулювати наступні 
рекомендації для вирішення поставленої задачі щодо розробки веб-сайту салону 
краси: 
1. Інтерактивні анімації: Використання інтерактивних анімацій, які 
реагують на дії користувача, значно підвищує взаємодію та зацікавленість. 
Важливо забезпечити, щоб такі елементи не перевантажували сайт та не 
впливали негативно на його продуктивність. 
2. Форми зворотного зв'язку: Чітке визначення призначення форм на 
сайті спрощує взаємодію користувачів і зменшує кількість помилок або 
непорозумінь. Це допоможе користувачам швидше знаходити необхідну 
інформацію і здійснювати бажані дії, наприклад, запис на процедури. 
3. Нагадування про записи: Автоматизація нагадувань про майбутні 
візити та процедури підвищує лояльність клієнтів. Такий підхід допомагає 
підтримувати постійний зв'язок з клієнтами та забезпечує більш високий рівень 
задоволення користувачів послугами салону. 
4. Адаптивний дизайн: Сайт має бути оптимізований для роботи на 
всіх типах пристроїв, включаючи мобільні телефони та планшети. Адаптивний 
дизайн забезпечує зручність користування сайтом незалежно від пристрою, що 
використовується, і підвищує охоплення потенційних клієнтів. 
5. Можливість запису: Допомагає користувачу записатися не  
розмовляючи з людьми, що допомагає не  використовувати додаткових 
робітників які б приймали дзвінки та записували людей. 
6. Кабінет користувача: Надає спеціальний кабінет для користувача в 
якому він може керувати своїми даними та записами, що дає користувачу 
можливість виконувати бажані операції не бентежачи інших 
     ЧДТУ 242280.003 ПЗ Лист 
      
19 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
7. Кабінет майстра: Майстер може керувати записами до нього, 
проглядати записи до нього та керувати ними, також може переглядати 
інформацію про клієнта який до нього записався 
Загалом, ці підходи дозволять створити не тільки красивий та 
функціональний сайт, але й ефективно використовувати його для підвищення 
задоволеності клієнтів та комерційного успіху салону краси. 
 
1.5 Виявлення потреби у покращенні систем 
 
Перш за все, потрібно створити форму запису, яка буде не лише легкою у 
використанні, але й зрозумілою для кожного користувача. Форма повинна бути 
чіткою та простою, без зайвого складнощів, щоб максимально спростити процес 
взаємодії з нею. Важливо, щоб форма не дублювалася, щоб уникнути плутанини 
та зберегти консистентність інтерфейсу. 
Додатково, рекомендується використати достатню кількість анімацій, щоб 
привернути увагу користувачів до веб-сайту та підвищити його рейтинг. 
Анімації можуть бути використані для підкреслення важливих елементів, 
покращення візуальної привабливості сторінок та забезпечення цікавого та 
захопливого досвіду для відвідувачів. Однак важливо забезпечити, щоб анімації 
були ретельно розроблені та не перевантажували сторінку, щоб не впливати на її 
швидкість завантаження та загальний користувацький досвід. 
 
Вибір технологій та підходів для власної розробки. 
Для вибору технологій та підходів для власної розробки, я вирішила 
скористатися фреймворком Symfony та шаблонізатором Twig. Обираючи 
Symfony, я врахувала його популярність та широкий функціонал, який надає 
широкі можливості для розробки на PHP. Цей фреймворк гарантує безпечність 
та стабільність проекту, а також сприяє швидкому розвитку завдяки вбудованим 
компонентам та зручним інструментам. 
Плюси обраного підходу також включають в себе: 
     ЧДТУ 242280.003 ПЗ Лист 
      
20 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
1. Розширюваність: Symfony має широкий вибір розширень та 
бібліотек, які дозволяють легко розширювати функціонал проекту за потреби. 
2. Спільнота: Як один з найпопулярніших фреймворків, Symfony має 
велику активну спільноту розробників, яка надає підтримку, допомогу та 
різноманітні ресурси для вирішення будь-яких проблем. 
3. Документація: Symfony має докладну та зрозумілу документацію, 
яка допомагає розробникам швидко засвоювати фреймворк та розвивати 
проекти. 
Вибір Symfony та Twig є розумним рішенням для створення надійних, 
безпечних та розширюваних веб-додатків з високоякісним інтерфейсом. 
 
Висновки до розділу 1: 
 
Проведено аналіз ринку веб-сайтів для салонів краси з метою визначення 
основних тенденцій та недоліків існуючих ресурсів. Виявлено, що багато з цих 
сайтів не відповідають сучасним стандартам користувацького досвіду, що вказує 
на необхідність створення більш ефективних рішень.  
     ЧДТУ 242280.003 ПЗ Лист 
      
21 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
РОЗДІЛ 2 
ВИБІР ЗАСОБІВ РОЗРОБКИ ТА ПРОЕКТУВАННЯ САЙТУ 
 
 
2.1 Призначення розроблюваної інформаційної системи. 
 
Веб-сайт "Golden Epil" розробляється з метою полегшення та 
рекламування салону краси "Golden Epil". Основними функціональними 
завданнями системи будуть: 
 Кабінет користувача: Цей розділ веб-сайту призначений для зручного 
керування записами клієнтів. Користувачі матимуть змогу переглядати свої 
записи, редагувати контактну інформацію, а також переглядати додаткові дані 
про салон краси, акції та іншу корисну інформацію. 
 Кабінет майстра: Цей розділ буде призначений для майстрів салону 
краси. Вони матимуть можливість створювати нові записи клієнтів, переглядати 
і керувати своїм розкладом, а також відміняти або змінювати інформацію про 
існуючі записи. 
 Сторінка для запису: Ця сторінка дозволить користувачам 
здійснювати онлайн-запис в салон краси "Golden Epil". Клієнти матимуть 
можливість вибирати доступний час та послуги, заповнювати контактну 
інформацію і здійснювати запис на зручний для них час. 
Додатково, система буде забезпечувати: 
 Зручний інтерфейс, який забезпечить зручне взаємодію користувачів з 
веб-сайтом. 
 Захист персональних даних користувачів згідно з вимогами 
законодавства про конфіденційність. 
 Інтеграцію з системами керування контентом для швидкої і легкої 
зміни та оновлення інформації на веб-сайті. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
22 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 Оптимізацію для пошукових систем, щоб забезпечити високий рівень 
видимості в Інтернеті та привернення нових клієнтів. 
Ця інформаційна система буде сприяти автоматизації та оптимізації роботи 
салону краси "Golden Epil", забезпечуючи зручність та ефективність як для 
клієнтів, так і для персоналу. 
 
2.2 Опис завдань, які передбачається вирішити за допомогою системи. 
 
Система "Golden Epil" розробляється для вирішення ряду завдань, 
спрямованих на покращення ефективності та зручності як для клієнтів, так і для 
персоналу салону краси "Golden Epil". Основні завдання системи включають: 
1. Забезпечення онлайн-запису: Система надасть можливість клієнтам 
здійснювати запис на процедури через Інтернет, що значно зекономить час як 
для клієнтів, так і для персоналу. 
2. Управління розкладом майстрів: Система дозволить майстрам 
ефективно керувати своїм розкладом, реагуючи на зміни та відміни записів 
клієнтів. 
3. Забезпечення доступу до інформації: Клієнти матимуть змогу 
переглядати актуальну інформацію про послуги, ціни, акції та розклад роботи 
майстрів через веб-сайт "Golden Epil". 
4. Покращення взаємодії з клієнтами: Система забезпечить можливість 
зберігати історію відвідувань клієнтів, що дозволить персоналу надати 
персоналізоване обслуговування та рекомендації. 
5. Оптимізація робочих процесів: Система автоматизує багато 
рутинних завдань, таких як формування звітів, обробка платежів та управління 
клієнтською базою даних, що дозволить персоналу салону краси більш 
ефективно використовувати свій час та ресурси. 
6. Збереження конфіденційності даних: Система забезпечить захист 
персональних даних клієнтів та даних про операції згідно з вимогами 
законодавства про конфіденційність. 
     ЧДТУ 242280.003 ПЗ Лист 
      
23 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
2.3 Визначення цільової аудиторії та їх потреб. 
 
Визначення цільової аудиторії та їх потреб є ключовим кроком у розробці 
системи "Golden Epil", оскільки це допомагає точно визначити, які 
функціональності та сервіси будуть найбільш корисними для користувачів. 
Основні складові цільової аудиторії та їх потреб включають: 
1. Клієнти салону краси "Golden Epil": 
o Люди, які цінують свій час і шукають зручні способи записатися на 
процедури. 
o Особи, які шукають якісні косметичні та естетичні послуги. 
o Клієнти, які бажають мати можливість швидко та легко отримати 
інформацію про послуги та акції салону. 
2. Майстри салону краси "Golden Epil": 
o Фахівці, які шукають ефективний спосіб керування своїм розкладом 
та клієнтами. 
o Майстри, які цінують можливість отримувати доступ до інформації 
про свої записи та клієнтів в будь-який зручний для них момент. 
3. Адміністратори та власники салону краси "Golden Epil": 
o Персонал, який шукає спосіб ефективно керувати робочими 
процесами та управляти клієнтською базою даних. 
o Власники салону, які бажають мати можливість контролювати та 
аналізувати роботу салону для покращення ефективності та прибутковості. 
З урахуванням цільової аудиторії та їх потреб система "Golden Epil" буде 
розроблена з врахуванням їх вимог та відповідатиме найвищим стандартам 
зручності, ефективності та безпеки. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
24 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
2.4 Формування вимог 
 
 Запис на процедуру: зручний інтерфейс для онлайн-запису на 
процедури, з можливістю вибору вільного часу та конкретного майстра. 
 Реєстрація та логін: система реєстрації дозволяє створити 
особистий кабінет, через який клієнт може керувати своїми записами та 
персональними даними. 
 Особистий кабінет: секція сайту, де клієнт може переглядати 
історію своїх відвідувань, управляти майбутніми записами та налаштуваннями 
облікового запису. 
 Каталог дизайнів та майстрів: вітрина з прикладами робіт та 
профілями майстрів, що дозволяє клієнтам ознайомитися з талантами та 
спеціалізацією кожного майстра. 
 Функція відгуків: можливість для клієнтів залишати відгуки про 
якість послуг та роботу майстрів, що сприяє підвищенню довіри до салону. 
 Інтеграція з месенджерами: система повідомлень через популярні 
месенджери, такі як Telegram, для нагадувань про записи та інформування про 
акції та спеціальні пропозиції. 
 
2.5 Конструювання дизайну сайту 
 
Дизайн веб-сайту "Golden Epil" повинен бути спроектований з 
урахуванням потреб та вподобань цільової аудиторії, а також забезпечувати 
зручність користування та привабливий зовнішній вигляд. Основні вимоги до 
дизайну веб-ресурсу включають: 
1. Мінімалістичний та сучасний дизайн: Веб-сайт повинен мати чистий 
та естетичний вигляд, з обережним використанням кольорів та графічних 
елементів, щоб не перенавантажувати сторінку інформацією. 
     ЧДТУ 242280.003 ПЗ Лист 
      
25 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
2. Інтуїтивно зрозуміла навігація: Система меню та навігаційні 
елементи повинні бути легкими для розуміння та навігації, щоб користувачі 
могли швидко знаходити потрібну інформацію. 
3. Адаптивний дизайн: Веб-сайт повинен бути повністю адаптований 
під різні пристрої та розміри екранів, забезпечуючи зручний перегляд як на 
комп'ютерах, так і на мобільних пристроях. 
4. Емоційний зв'язок: Дизайн повинен створювати позитивне та 
довірливе сприйняття бренду "Golden Epil", відображаючи його цінності та 
атмосферу. 
5. Швидкість та продуктивність: Веб-сайт повинен працювати швидко 
та ефективно, з мінімальними часами завантаження сторінок та оптимізованим 
використанням ресурсів. 
6. Зручність користування формами та інтерактивними елементами: 
Форми для запису та інші інтерактивні елементи повинні бути легкими для 
використання та заповнення, з мінімальною кількістю обов'язкових полів. 
 
Встановлення кольорової гами та візуальних елементів. 
Під час створення концепції дизайну для салону було надзвичайно 
важливо врахувати не лише естетичний аспект, але й психологічні впливи 
кольорів на сприйняття клієнтів. Золотистий відтінок (#b69256) обрано як 
основний кольор не лише через його розкішний вигляд, але й через його асоціації 
з елегантністю та розкошшю, що створює атмосферу вишуканості та 
престижності. Комбінація з білим кольором надає простору світлоту та 
прозорості, підкреслюючи його просторовість та чистоту. 
У свою чергу, проміжні кольори, такі як #a4844e для кнопок при наведені 
та світло сірий колір для полос девайдерів, були обрані з метою додати відтінків 
та глибини до дизайну. Їхнє використання допомагає забезпечити більшу 
контрастність та різноманітність у візуальному сприйнятті, роблячи інтерфейс 
більш привабливим та цікавим для клієнтів. 
     ЧДТУ 242280.003 ПЗ Лист 
      
26 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Щодо шрифтів, чорний колір був обраний з міркувань чіткості та зручності 
читання. Він контрастує з фоном і підкреслює важливість текстової інформації, 
роблячи її більш помітною та легко зрозумілою для користувачів. 
Для створення більш привабливого дизайну, ми обрали стратегію, що 
передбачає використання багатьох банерів, які будуть акцентувати увагу клієнтів 
на найбільш значущих аспектах салону. Ці банери не лише будуть ілюструвати 
різноманітні послуги та продукти, що пропонуються, але й створюватимуть 
особисті зв'язки, використовуючи фотографії наших майстрів. 
Завдяки цьому підходу, ми збільшуємо емоційну привабливість нашого 
веб-сайту, пропонуючи клієнтам не лише інформацію, але й можливість 
познайомитися з людьми, які стоять за процесом, та переконатися у їхніх 
професійних навичках та досвіді. Це робить наш дизайн більш особистим та 
привабливим, створюючи позитивне враження від візиту нашого сайту та, в 
кінцевому рахунку, збільшуючи ймовірність конверсії та задоволення клієнтів. 
 
Визначення шрифтів та їх розмірів. 
Основним шрифтом для проєкту обранний шрифт, який автоматично 
використовується Bootstrap, оскільки він забезпечує чудову сумісність із різними 
пристроями та браузерами, а також має професійний вигляд. Використання цього 
шрифту дозволяє забезпечити консистентність та читабельність контенту на 
сайті. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
27 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
В проекті буде використано кілька розмірів шрифтів, щоб забезпечити 
ієрархію інформації та покращити користувацький досвід. Ось приклади: 
- Заголовок (H1): Використовується для основного заголовка сторінки, 
найбільший за розміром шрифт, що виділяє основну тему або ідею контенту. 
Розмір цього шрифту може варіюватися, але він має бути достатньо великим, 
щоб привернути увагу користувача. 
- Підзаголовок (H2, H3, H4): Використовуються для розділення 
контенту на секції та підсекції, допомагаючи користувачу легше навігувати 
структурою інформації. Розміри шрифтів підзаголовків зменшуються зі 
збільшенням рівня заглиблення. 
- Звичайний текст: Стандартний розмір шрифту для основного тексту. 
Він має бути достатньо читабельним і комфортним для тривалого читання. 
Зазвичай це розмір від 14 до 16 пікселів. 
- Додаткові текстові елементи: Це можуть бути примітки, цитати, 
підписи під фотографіями, текст кнопок тощо. Для кожного з цих елементів 
використовуються свої розміри шрифтів, але вони мають бути інтегровані так, 
щоб доповнювати загальний дизайн та забезпечувати єдність стилістики. 
При визначенні розмірів шрифтів важливо враховувати доступність та 
зручність для всіх користувачів, включаючи людей із порушеннями зору. 
Оптимальний вибір розмірів та шрифтів сприятиме підвищенню загальної якості 
веб-доступності сайту. 
 
2.6 Вибір засобів розробки та ПЗ 
 
Вибір технологічного стеку для проекту: 
1. Основні технології: Вибір фреймворка та шаблонізатора критично 
важливий для успішної реалізації проекту. Symfony було обрано як основний 
фреймворк через його потужні функціональні можливості та високий рівень 
безпеки. Twig використовується як шаблонізатор, що забезпечує гнучке 
управління вмістом і відділяє логіку додатку від його презентації. 
     ЧДТУ 242280.003 ПЗ Лист 
      
28 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
2. Безпека та стабільність: Завдяки використанню Symfony можна 
гарантувати розвиток надійного проекту. Цей фреймворк допомагає уникнути 
багатьох загроз безпеці та збоїв системи, використовуючи сучасні практики 
безпеки та кодування. 
3. Масштабування та адаптивність: Symfony надає можливість 
масштабувати проект відповідно до зростаючих потреб та вимог. Фреймворк 
дозволяє легко інтегрувати нові компоненти, забезпечуючи при цьому 
збереження високої продуктивності та гнучкості додатку. 
4. Підтримка спільноти: Один з найбільших активів Symfony - це 
велика і активна спільнота розробників. Це спільнота не тільки сприяє 
вирішенню технічних питань, а й постійно розвиває фреймворк, забезпечуючи 
його актуальність. 
5. Доступність ресурсів для навчання та розвитку: Велика кількість 
документації та навчальних ресурсів для Symfony та Twig робить їх привабливим 
вибором для розробників, які хочуть швидко розвивати свої навички. Це сприяє 
швидкому засвоєнню технології та її ефективному використанню в проекті. 
 
Обирання ПЗ для розробки:  
 Середовище розробки 
Вибір правильного середовища розробки (IDE) є ключовим фактором для 
будь-якого розробника програмного забезпечення, адже воно значно впливає на 
продуктивність, зручність роботи та загальний досвід розробки. 
Середовищем розробки було обрано PHPShtorm через його високу 
ефективність та  безплатність для студентів, загалом це ПЗ маэ багато переваг:  
o Висока продуктивність: PHPShtorm відомий своєю швидкістю та 
плавністю роботи. Він легко справляється з великими файлами коду, 
пропонуючи безліч функцій, які оптимізують робочий процес. 
o Безкоштовність для студентів: Для студентів PHPShtorm 
доступний безкоштовно, що робить його чудовим вибором для тих, хто робить 
перші кроки у світі PHP-розробки. 
     ЧДТУ 242280.003 ПЗ Лист 
      
29 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
o Широкий функціонал: PHPShtorm пропонує широкий спектр 
функцій, які полегшують життя розробників PHP, включаючи автозаповнення 
коду, перевірку синтаксису, рефакторинг коду, відладку, профілювання, 
підтримку Git та інших систем контролю версій. 
o Можливість встановлення плагінів: Завдяки активній спільноті 
розробників PHPShtorm має безліч плагінів, які розширюють його 
функціональність. Для розробки на Symfony доступні плагіни для 
автозаповнення коду, відладки та профілювання, що значно економить час та 
зусилля. 
o Зручний інтерфейс: PHPShtorm має інтуїтивно зрозумілий 
інтерфейс, який легко налаштувати під індивідуальні потреби. 
o Досвід користувачів: Розробники, які використовують PHPShtorm 
протягом тривалого часу, відзначають значне прискорення роботи та 
покращення своїх навичок завдяки цьому IDE. 
Щоб прискорити розробку необхідно окрім багових плагінів які ставляться 
IDE необхідно обрати які найкраще підходять для проекту. Для даного проекту 
було обрано наступні плагіни: 
     ЧДТУ 242280.003 ПЗ Лист 
      
30 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
Рисунок 2.1 Плагіни для IDE 
Оглянемо деякі плагіни що будуть використовуватися в проекті:  
 
Env files support: 
Цей плагін додає підтримку файлів .env до вашого проекту PHP. Це 
дозволяє вам легко керувати налаштуваннями вашого додатку без жорсткого 
кодування їх у ваш код. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
31 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Batch Scripts Support: 
Цей плагін додає підтримку пакетних сценаріїв до вашого проекту PHP. Це 
дозволяє вам автоматизувати завдання, такі як компіляція коду, запуск тестів та 
розгортання вашого додатку. 
Ideolog: 
Цей плагін додає підтримку IDEolog до вашого проекту PHP. IDEolog - це 
інструмент статичного аналізу коду, який може допомогти вам знайти потенційні 
проблеми у вашому коді, такі як помилки, попередження та неефективні 
конструкції. 
Symfony Support: 
Цей плагін додає підтримку Symfony до вашого проекту PHP. Це дозволяє 
вам використовувати функції Symfony у вашому коді, такі як маршрутизація, 
контролери та шаблони. 
Refact.ai: 
Цей плагін додає підтримку Refact.ai до вашого проекту PHP. Refact.ai - це 
інструмент рефакторингу коду, який може допомогти вам автоматизувати такі 
завдання, як перейменування змінних, витяг методів та рефакторинг умов. 
Stylus: 
Цей плагін додає підтримку Stylus до вашого проекту PHP. Stylus - це 
препроцесор CSS, який дозволяє вам використовувати змінні, міксини та інші 
функції для написання більш лаконічного та підтримуваного CSS-коду. 
 
Додаткове ПЗ для перевірки роботи : 
У рамках цього проекту не передбачається використання складних 
алгоритмів або високої ступені інтеграції, тому глибоке тестування не 
вимагається. Відповідно, для перевірки функціональності та взаємодії із 
сервером будуть використані базові інструменти. Зокрема, Postman — це 
додаток для тестування API, який дозволяє відправляти запити до веб-сервера та 
аналізувати відповіді. Він є відмінним рішенням для швидкої перевірки і 
розробки API. Для тестування користувацького інтерфейсу та взаємодії веб-
     ЧДТУ 242280.003 ПЗ Лист 
      
32 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
сайту з користувачем рекомендується використовувати анонімний режим у 
браузері. Найкраще використовувати Google Chrome, оскільки цей браузер є 
найпопулярнішим серед користувачів, а тому перевірка в ньому дозволить 
забезпечити оптимальну сумісність. Використання анонімного режиму 
забезпечує відсутність локальних кешів та кукі, що допомагає уникнути 
можливих помилок при завантаженні контенту. 
 
 Робота з БД та Doctrine 
БД дуже важлива частина проекту, оскільки цей проект не є сайтом 
візиткою, планується зберігати дуже багато даних таких  дані юзерів  та майстрів, 
список послуг та записів тощо. 
В Symfony в якості  ORM використовується Doctrine. 
Doctrine ORM - це бібліотека для роботи з об'єктно-реляційним 
відображенням (ORM) в PHP. ORM - це технологія, яка дозволяє взаємодіяти з 
базою даних за допомогою об'єктно-орієнтованого коду, приховуючи деталі 
роботи з базою даних. 
Основні особливості Doctrine ORM : 
1. Відображення об'єктів на таблиці бази даних: Doctrine дозволяє 
вам описувати структуру вашої бази даних за допомогою об'єктів PHP. Класи 
PHP відповідають таблицям бази даних, а їх властивості - полям цих таблиць. 
2. Мапування зв'язків між об'єктами: Doctrine дозволяє вам 
описувати зв'язки між об'єктами (наприклад, один до багатьох, багато до 
багатьох тощо) та використовувати їх у вашому коді, що спрощує роботу з 
відносинами між даними. 
3. Мови запитів: Doctrine надає потужний інструментарій для 
створення запитів до бази даних, використовуючи об'єктно-орієнтований 
синтаксис або мову запитів Doctrine Query Language (DQL), яка є аналогом SQL, 
але працює з об'єктами, а не з таблицями. 
4. Підтримка транзакцій та кешування: Doctrine дозволяє вам 
використовувати транзакції для забезпечення цілісності даних в базі даних. Вона 
     ЧДТУ 242280.003 ПЗ Лист 
      
33 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
також підтримує кешування, що дозволяє підвищити продуктивність вашого 
додатка за рахунок кешування часто використовуваних запитів та даних. 
5. Міграції бази даних: Doctrine має вбудовані засоби для створення та 
керування міграціями бази даних. Це дозволяє зручно оновлювати схему бази 
даних разом з розвитком додатка. 
 
Висновки до розділу 2: 
 
У даному розділі детально розглядається процес проектування 
інформаційної архітектури. Звертається увага на значення зовнішнього дизайну 
та внутрішньої логіки веб-сайту для оптимізації користувацького досвіду та 
забезпечення ефективної взаємодії з відвідувачами.  
     ЧДТУ 242280.003 ПЗ Лист 
      
34 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
РОЗДІЛ 3 
 РОЗРОБКА СИСТЕМИ 
 
 
3.1 Підготовка середовища розробки: 
 
Для розробки проекту на Symfony було обрано програмне забезпечення 
XAMPP як локальний сервер. Хоча Symfony надає власний вбудований сервер, 
який можна запустити за допомогою команди symfony server:start, такий підхід 
не завжди є оптимальним для всіх аспектів розробки. Вбудований сервер 
Symfony створений для забезпечення зручності розробки і вже налаштований 
таким чином, щоб максимально спростити робочий процес. Однак, це може 
призвести до того, що деякі потенційні проблеми, які можуть виникнути під час 
перенесення проекту на реальний хостинг, можуть залишитися непоміченими. 
Зокрема, бандл Profiler bundle, який використовується в Symfony для 
полегшення розробки, може не функціонувати належним чином без коректно 
налаштованого файлу .htaccess. Вбудований сервер Symfony автоматично 
вирішує ці конфігураційні питання, але це може не відобразити реальну ситуацію 
на стандартному сервері. 
Це робить XAMPP ідеальним вибором для розробників, які шукають 
стабільність і відтворюваність середовища на різних платформах, а також 
бажають мати повний контроль над усіма аспектами серверної конфігурації. 
Таким чином, використання XAMPP не тільки сприяє більшій надійності та 
безпеці проекту, але й забезпечує глибоке розуміння роботи веб-серверів і 
потенційних викликів, пов'язаних з розгортанням веб-додатків. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
35 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Створення локального серверу 
Для початку роботи необхідно розвернути середовище в котрому й буде 
функціонувати сайт. Для цього необхідного встановити та налаштувати сайт. 
Для того, щоб розгорнути сайт локально за допомогою XAMPP і налаштувати 
його для розробки, потрібно пройти кілька етапів, від завантаження до 
конфігурації файлу хостів. Ось поетапна інструкція: 
Крок 1: Завантаження XAMPP: 
1. Перейдіть на офіційний сайт XAMPP - Apache Friends. 
2. Виберіть версію XAMPP, яка підходить для вашої операційної 
системи (Windows, MacOS або Linux). 
3. Завантажте та запустіть інсталятор. 
Крок 2: Встановлення XAMPP: 
1. Запустіть інсталяційний файл та дотримуйтесь інструкцій на екрані. 
2. Виберіть компоненти, які вам потрібно встановити. Для базової 
розробки сайту рекомендується вибрати Apache, MySQL, PHP та phpMyAdmin. 
3. Вкажіть каталог для встановлення XAMPP і продовжіть інсталяцію. 
4. Дочекайтеся завершення встановлення та запустіть XAMPP Control 
Panel. 
Крок 3: Запуск серверів: 
1. Запустіть XAMPP Control Panel. 
2. Натисніть кнопки Start поруч із Apache та MySQL для запуску 
серверів. 
3. Перевірте, чи сервери працюють, відкривши у браузері адресу 
http://localhost. 
Крок 4: Додавання проекту: 
1. Розмістіть файли вашого сайту у папці htdocs, яка знаходиться в 
каталозі, де встановлено XAMPP. Наприклад, C:\xampp\htdocs\myproject. 
2. Можете створити підкаталог в htdocs для кожного проекту, 
наприклад, C:\xampp\htdocs\myproject для вашого сайту. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
36 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Крок 5: Редагування файлу hosts (для Windows): 
1. Відкрийте файл hosts для редагування. Файл зазвичай знаходиться в 
C:\Windows\System32\drivers\etc\hosts. 
2. Відкрийте файл за допомогою текстового редактора з 
адміністративними правами. 
3. Додайте новий рядок: 127.0.0.1 myproject.local, де  
myproject.local — це бажана назва вашого локального сайту. 
4. Збережіть файл і закрийте редактор. 
Крок 6: Редагування Apache конфігураційного файлу 
1. Відкрийте файл httpd-vhosts.conf, який зазвичай знаходиться в 
C:\xampp\apache\conf\extra\. 
2. Додайте наступні рядки: 
<VirtualHost *:80> 
ServerAdmin [email protected] 
DocumentRoot "C:/xampp/htdocs/myproject" 
ServerName myproject.local 
ServerAlias www.myproject.local 
ErrorLog "logs/myproject.local-error.log" 
CustomLog "logs/myproject.local-access.log" common 
</VirtualHost> 
Рисунок 3.1 Налаштування віртуал-хоста 
 
Збережіть зміни і перезапустіть Apache через XAMPP Control Panel. 
 
Розгортання Symfony через Composer 
Для того аби почати роботу з фреймворком Symfony необхідно встановити 
всі необхідні компоненти(бібліотеки) за допомогою пакет менеджера Composer. 
Переходимо до директорії яку ми раніше створили в ксампі для нашого 
проекта, та вводимо команду «composer create-project symfony/skeleton» та 
чекаємо поки встановляться всі бібліотеки: 
     ЧДТУ 242280.003 ПЗ Лист 
      
37 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
Рисунок 3.2 Розгортання проекта  
 
Після завершення інсталяції необхідно створити файл .htaccess в 
директорії \public\.htaccess та заповнити його наступним чином:  
 
<IfModule mod_rewrite.c> 
    Options -MultiViews 
    RewriteEngine On 
    RewriteCond %{REQUEST_FILENAME} !-f 
    RewriteRule ^(.*)$ index.php [QSA,L] 
</IfModule> 
Рисунок 3.3 Налаштування .htaccess 
 
Це правило забезпечує, що всі HTTP запити, які не відповідають дійсним 
файлам, будуть перенаправлені до index.php, де Symfony займеться розбором 
URL. 
Тепер необхідно перевірити як працюють налаштування для цього 
необхідно виконати наступне: 
Відкрийте веб-браузер і перейдіть до http://localhost/myproject/public/ для 
перевірки, що Symfony успішно відповідає на запити. 
Ви маєте побачити стандартну сторінку вітання Symfony, що підтверджує 
правильність налаштувань. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
38 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Обрання додаткових бібліотек: 
Хоча інсталяція Symfony за допомогою команди «composer create-project 
symfony/skeleton» вже встановлює всі основні бібліотеки, необхідні для 
базового функціонування фреймворку, специфіка проекту може вимагати 
додаткових бібліотек для розширення функціональності. Для цього проекту було 
обрано наступні додаткові бібліотеки: 
"sonata-project/admin-bundle": "^4.30", 
"sonata-project/doctrine-orm-admin-bundle": "^4.17", 
"twig/extra-bundle": "^2.12|^3.0", 
Рисунок 3.4 Додаткові бібліотеки 
 
1. "sonata-project/admin-bundle": "^4.30 — цей пакет надає 
потужний інтерфейс для адміністрування в Symfony. Він дозволяє швидко 
створювати адміністративні панелі для управління сутностями (entities), що є 
частиною вашої доменної моделі. Він також пропонує широкі можливості 
налаштування та інтеграцію з Doctrine ORM. 
2. "sonata-project/doctrine-orm-admin-bundle" — це розширення для 
sonata-project/admin-bundle, що забезпечує інтеграцію з Doctrine ORM.  
3. "twig/extra-bundle" — є частиною екосистеми Twig, який додає 
додаткові розширення до базового функціоналу Twig у Symfony проектах. 
Обидві ці бібліотеки sonata-project допомагають автоматизувати 
створення комплексних адміністративних розділів на сайті, значно знижуючи 
кількість ручної роботи, необхідної для реалізації бекенду. Це, в свою чергу, 
пришвидшує розробку та допомагає зосередитися на більш специфічних задачах 
проекту, не витрачаючи час на створення стандартних адміністративних 
функцій. 
Натомість twig/extra-bundle  додає декілька ключових особливостей та 
функціональних можливостей: 
     ЧДТУ 242280.003 ПЗ Лист 
      
39 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
1. Internationalization Extension: Дозволяє легко інтегрувати 
багатомовність у шаблони Twig. Включає фільтри та функції для роботи з 
перекладами. 
2. HTML, CSS, і JavaScript minifiers: Покращує продуктивність 
застосунку шляхом мінімізації HTML, CSS, і JavaScript коду прямо у шаблонах 
Twig. 
3. Markdown Extension: Включає фільтри, які дозволяють 
використовувати Markdown у Twig шаблонах. Це корисно для рендерингу 
тексту, який був заздалегідь відформатований у Markdown. 
4. Time Extension: Додає функціональність для роботи з датами і часом 
у шаблонах, забезпечуючи зручні фільтри та функції для форматування і 
маніпуляцій з датами. 
 
3.2 Робота з БД, формування зав’язків та таблиць 
 
Для реалізації сайту необхідно створити наступні таблиці бази баних: 
1. golden_epi_appointments_status - Містить статуси для записів на 
прийом або зустрічі. Кожен статус має назву англійською та українською 
мовами. 
2. golden_epi_appointments - Записи на прийоми або зустрічі. Зв'язана 
з послугами, користувачами і статусами. Включає інформацію про час і ціну 
послуги. 
3. golden_epi_services - Послуги, які надає організація. Включає опис, 
зображення, ціну, категорію та тип послуги. 
4. golden_epi_user_details - Детальна інформація про користувачів, 
включаючи контактні дані в соціальних мережах, професію, навички, місто 
проживання тощо. 
5. golden_epi_user - Основна інформація про користувачів системи, 
така як ім'я, прізвище, електронна пошта, телефон, ролі в системі та логотип. 
     ЧДТУ 242280.003 ПЗ Лист 
      
40 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
6. golden_epi_courses_info - Інформація про курси, включаючи 
інструктора, основні теми та навички, які розвиваються на курсі. 
7. golden_epi_courses_student - Записи студентів на курси, що 
включають статус участі, часові рамки курсу. 
8. golden_epi_courses - Курси, що пропонуються організацією. Містить 
інформацію про назву курсу, його опис, час початку та закінчення, статус та тип 
курсу. 
9. golden_epi_news - Новини або оголошення від організації, що 
містять дату публікації, заголовок, текст новини та посилання на зображення. 
10. golden_epi_form_messages - Повідомлення від користувачів через 
форми зворотного зв'язку, що містять інформацію про користувача та текст 
повідомлення. 
Зв’язки між таблицями можна розглянути на рисунку з схемою структури 
БД: 
 
Рисунок 3.5 Схема бази даних  
 
Для ефективної взаємодії з базою даних, крім сутностей, необхідні 
репозиторії, які здійснюють операції з даними, такі як збереження, пошук та 
кастомні запити. В ідеальному випадку рекомендується створити базовий 
     ЧДТУ 242280.003 ПЗ Лист 
      
41 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
репозиторій, що включає загальні методи (збереження, оновлення даних 
сутностей), від якого в подальшому можуть наслідувати інші, більш специфічні 
репозиторії.  
Такий підхід часто використовується у великих масштабних проектах, де 
вимагається висока ступінь реюзабельності та модулярності компонентів. Однак, 
під час початкової фази розробки цього проекту, вважалося, що такий обсяг 
архітектурних вимог може бути надмірним, враховуючи обмежений розмір і 
складність проекту. Тому було вирішено дотримуватися більш простої 
архітектури, де кожна сутність має відповідний репозиторій. Цей підхід сприяє 
зосередженню на функціональності без ускладнення структури системи, 
зберігаючи при цьому гнучкість у розширенні та масштабуванні проекту у 
майбутньому. 
Тож в результаті ми маємо ось такі сутності та репозиторії: 
 
Рисунок 3.6 Структура сутностей в проекті 
 
     ЧДТУ 242280.003 ПЗ Лист 
      
42 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 3.3 Використання Symfony Sonata для створення адмінки 
 
 Основні характеристики Symfony Sonata: 
При розробці адміністративної панелі для веб-проектів, критично важливо 
забезпечити не тільки широкий спектр функціональності, але й високий рівень 
безпеки та зручність користування. Ці аспекти є вирішальними для ефективного 
управління контентом і даними користувачів. Завдання створення адмінки може 
бути особливо складним і трудомістким, особливо в умовах обмеженого 
бюджету і часу на проектах. Тому розробники часто вдаються до використання 
готових рішень, які дозволяють значно прискорити процес розробки. 
Однією з популярних інструментальних бібліотек для Symfony є Sonata 
Admin Bundle. Цей пакет надає широкі можливості для створення 
структурованих і інтуїтивно зрозумілих адміністративних інтерфейсів, які 
можуть бути легко адаптовані під конкретні потреби вашого проекту. Sonata 
Admin використовує модель конфігурації через PHP або YAML, що робить її 
надзвичайно гнучкою. 
 
Основні характеристики Sonata Admin: 
 Гнучка настройка: 
Sonata дозволяє детально налаштувати вигляд та поведінку кожної частини 
адміністративного інтерфейсу. Можна легко налаштувати список, створення, 
перегляд та редагування записів. 
 Інтеграція з Doctrine ORM: 
Соната ефективно інтегрується з Doctrine ORM, що дозволяє легко 
управляти даними через об'єктно-орієнтовані моделі. 
 Підтримка різних типів даних: Підтримує різноманітні типи даних, 
включаючи моделі, зв'язки між ними та складніші структури. 
Розширення і кастомізація: Sonata Admin має активну спільноту та багато 
розширень, які дозволяють додавати новий функціонал або модифікувати 
існуючий. 
     ЧДТУ 242280.003 ПЗ Лист 
      
43 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 Юзабельність: 
Sonata Admin пропонує продуманий UI/UX, який полегшує 
адміністрування сайту і дозволяє користувачам швидко знаходити необхідну 
інформацію і виконувати потрібні дії. Інтерфейс можна адаптувати під вимоги 
бренду або конкретні вимоги замовника, забезпечуючи при цьому зручність і 
ефективність роботи. 
 Безпека: 
Sonata Admin включає засоби для регулювання доступу та ролей 
користувачів, що дозволяє встановлювати детальні дозволи для різних розділів 
адмінки та забезпечує високий рівень безпеки обробки даних. 
Використання Sonata Admin Bundle в проектах на Symfony не тільки 
спрощує розробку адміністративних панелей, але й значно підвищує швидкість 
розробки, що є важливим фактором в умовах обмежених ресурсів та вимог до 
швидкості запуску проектів. 
 
Налаштування та встановлення: 
Перед тим як приступити до встановлення Sonata Admin Bundle, важливо 
переконатись, що ваш проект на Symfony готовий до інтеграції. В першу чергу, 
це вимагає наявності встановленого і налаштованого Composer, який є 
стандартним інструментом для управління залежностями в PHP. 
Встановлення Sonata Admin Bundle можна розпочати з виконання 
наступної команди у терміналі:  composer require sonata-project/admin-bundle. 
Ця команда встановить останню стабільну версію Sonata Admin Bundle разом з 
усіма необхідними залежностями. Symfony Flex автоматично налаштує 
більшість конфігураційних файлів, що значно спрощує процес інтеграції. 
 
Конфігурація: 
Після встановлення, перевірте, чи Symfony Flex коректно додав необхідні 
конфігураційні файли. Якщо це не так, вам потрібно буде вручну додати 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
44 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 відповідні конфігураційні блоки у ваші конфігураційні файли. Наприклад, ви 
могли б додати налаштування бандла до вашого файлу config/bundles.php, а 
також налаштувати зв'язок з базою даних і користувачами у 
config/packages/sonata_admin.yaml. 
Ось витяг з документації бандлу про те як повинні виглядати 
налаштування: 
 
Рисунок 3.7 Приклад налаштування Sonata  в bundles.php 
 
 
Рисунок 3.8 Приклад налаштування Sonata  в sonata_admin.yaml 
 
 
Рисунок 3.9 Приклад налаштування роутів Sonata  в sonata_admin.yaml 
     ЧДТУ 242280.003 ПЗ Лист 
      
45 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Перші кроки після встановлення: 
Створення адміністративних класів: Визначте адміністративні класи для 
кожної з сутностей, які ви хочете адмініструвати через Sonata Admin. Ці класи 
контролюють візуалізацію форм, списків і дій для кожної сутності. Ось приклад 
такого налаштування: 
final class UserAdmin extends AbstractAdmin 
{ 
    protected function configureFormFields(FormMapper $form): void 
    {$form->add('id', TextType::class) 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('phone') 
            ->add('logo');} 
    protected function configureDatagridFilters(DatagridMapper $datagrid): void 
    {$datagrid 
            ->add('id') 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('phone') 
            ->add('logo');} 
    protected function configureListFields(ListMapper $list): void 
    {$list->addIdentifier('id') 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('roles') 
            ->add('phone') 
            ->add('logo');} 
    ... 
Рисунок 3.10 Клас для налаштування сутності юзера в Sonata 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
46 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Результатом такого коду стане можливість налаштування таблиці User 
через адмін панель: 
 
Рисунок 3.11 Список записів в таблиці юзера 
 
 
Рисунок 3.12 Редагування запису 
     ЧДТУ 242280.003 ПЗ Лист 
      
47 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Налаштування безпеки: Конфігуруйте правила доступу до адмін-панелі, 
визначаючи ролі та дозволи для різних типів користувачів. 
До адмінки повинні мати доступ тільки адмін та розробник, тупо необхідно 
в конфігурації це обов’язково прописати: 
role_hierarchy: 
    ROLE_USER: ROLE_USER 
    ROLE_ADMIN: ROLE_ADMIN 
    ROLE_SUPER_ADMIN: [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] 
access_control: 
    - { path: ^/master_cabinet, roles: [ ROLE_ADMIN, ROLE_MASTER ] } 
    - { path: ^/user_cabinet, roles: [ ROLE_ADMIN, ROLE_USER] } 
    - { path: ^/admin, roles: [ ROLE_ADMIN ] } 
Рисунок 3.13 Налаштування доступу до шляхів 
 
Завершення цих кроків забезпечить стабільну основу для подальшої 
роботи з вашою адміністративною панеллю, дозволяючи ефективно управляти 
контентом і користувачами вашого сайту чи додатку. 
 
3.4 Створення дизайну сайту та його архітектури сторінок 
 
За допомогою дослідження різноманітних джерел та сайтів, які були 
використані як натхнення, ми маємо можливість взяти у них деякі елементи, які 
відзначаються чудовою реалізацією. Ці елементи можуть включати в себе дизайн 
кнопок, стилі тексту, анімаційні ефекти, розташування елементів на сторінці та 
багато іншого. 
Використовуючи ці елементи як вихідну точку, ми можемо створити 
грубий нарис власного сайту. Цей грубий нарис може включати основну 
структуру сторінки, розташування основних елементів, загальний стиль 
оформлення тощо. 
Крім того, важливо пам'ятати, що використання цих елементів відповідно 
до власних потреб і бренду дозволить нам створити унікальний дизайн, який 
відображає нашу особливість і привабливість для наших користувачів. 
     ЧДТУ 242280.003 ПЗ Лист 
      
48 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Перш ніж переходити до розробки, важливо також звернути увагу на 
адаптивність дизайну, щоб забезпечити зручний доступ до нашого сайту на 
різних пристроях, від комп'ютерів до мобільних телефонів. 
 
Рисунок 3.14 Базовий макет сайту 
 
Тепер отримавши такий грубий нарис, потрібно переговирити з клієнтом, 
щоб отримати від нього конкретні побажання.  
Під час створення концепції дизайну для салону було надзвичайно 
важливо врахувати не лише естетичний аспект, але й психологічні впливи 
кольорів на сприйняття клієнтів. Золотистий відтінок (#b69256) обрано як 
основний колір не лише через його розкішний вигляд, але й через його асоціації 
з елегантністю та розкішшю, що створює атмосферу вишуканості та 
престижності. Комбінація з білим кольором надає простору світла та прозорості, 
підкреслюючи його просторовість та чистоту. 
У свою чергу, проміжні кольори, такі як #a4844e  для кнопок при наведені 
та світло сірий колір для полос девайдерів, були обрані з метою додати відтінків 
та глибини до дизайну.  
     ЧДТУ 242280.003 ПЗ Лист 
      
49 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Щодо шрифтів, чорний колір був обраний з міркувань чіткості та зручності 
читання. Він контрастує з фоном і підкреслює важливість текстової інформації, 
роблячи її більш помітною та легко зрозумілою для користувачів. 
Для створення більш привабливого дизайну, було обранно стратегію, що 
передбачає використання багатьох банерів, які будуть акцентувати увагу клієнтів 
на найбільш значущих аспектах салону. Ці банери не лише будуть ілюструвати 
різноманітні послуги та продукти, що пропонуються, але й створюватимуть 
особисті зв'язки, використовуючи фотографії наших майстрів. 
Матеріали, надані клієнтом, стали цінним джерелом натхнення для 
створення багатьох банерів, які не лише передають інформацію, але й створюють 
приємну атмосферу на сайті. Фотографії та інші матеріали дозволили нам 
створити образи, що відображають натуральність і спокій, а також передають 
дружній настрій. Ці банери створюють позитивний перший враження від сайту, 
роблячи його привабливим і затишним місцем для відвідувачів. Такий підхід 
допомагає підсилити відчуття довіри та зручності, що важливо для успішної 
взаємодії з користувачами. 
Результатом став привабливий макет, який схвалив клієнт та деякі інші 
спеціалісти (Макети всіх сторінок в Додатку А):  
  
     ЧДТУ 242280.003 ПЗ Лист 
      
50 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
 
Рисунок 3.15 Макет сайту  
 
При розмові з клієнтом важливо зрозуміти які сторінки він бажає бачити у 
фінальному продукті. Для цього можна використовувати архітектурну схему 
сторінок, яка візуалізує структуру сайту та допомагає наочно продемонструвати 
зв'язки між окремими елементами. Наприклад, у проекті салону краси «Golden 
Epil», схема архітектури включає головну сторінку, з якої можна перейти до 
сторінки про нас, сторінки курсів, сторінки запису на процедуру, а також 
сторінок логіна і реєстрації. Після авторизації користувачі мають доступ до 
особистих кабінетів — кабінету користувача та кабінету майстра, залежно від 
ролі. Така схема допомагає не лише у визначенні структури сайту, а й у 
плануванні навігації та інтерфейсу, забезпечуючи логічне та інтуїтивно 
зрозуміле взаємодію з сайтом: 
     ЧДТУ 242280.003 ПЗ Лист 
      
51 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
Рисунок 3.16 Архітектура сайту 
 
3.5 Створення шаблонів та розробка контролерів сторінок 
 
Контролери одна з найголовніших частин Symfony, вони відповідають за 
реалізацію логіки при викликі  певного урл. На прикладі головної сторінки 
розглянемо як працювати з контролерами та шаблонами. 
Почати розробку проекта необхідно з написання мейн контролеру який 
буде опрацьовувати звернення до головної сторінки: 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
52 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
<?php 
 
namespace App\Controller; 
 
use App\Model\ServicesType; 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\Routing\Annotation\Route; 
 
class MainController extends AbstractController 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
 
    public function __construct(UserRepository $userRepository, ServicesRepository 
$servicesRepository) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
    } 
 
    #[Route('/', name: 'home')] 
    public function index() 
    { 
        $masters = $this->userRepository->findByRole('ROLE_MASTER'); 
        $services = $this->servicesRepository->findServicesByNumber(5); 
        return $this->render('homepage.html.twig', [ 
            'masters' => $masters, 
            'services' =>  $services, 
        ]); 
    } 
 
} 
Рисунок 3.17 Контролер головної сторінки 
 
Цей код є контролером Symfony, який відповідає за обробку запитів на 
головну сторінку додатка. Давайте розберемо кожен його елемент: 
1. Простір імен (namespace): namespace App\Controller; - це місце, де 
зберігаються файли цього контролера. Symfony рекомендує розміщувати 
контролери в папці src/Controller. 
2. Клас контролера (class MainController): Цей клас містить всі 
методи, які обробляють різні запити, що надходять на вашу програму. В даному 
випадку, це index(), який обробляє запити на головну сторінку. 
3. Залежності (dependencies) та конструктор: Контролер має дві 
приватні залежності - UserRepository та ServicesRepository, які ініціалізуються 
через конструктор контролера. Це робиться для того, щоб мати можливість 
викликати методи цих репозиторіїв для отримання даних з бази даних. 
     ЧДТУ 242280.003 ПЗ Лист 
      
53 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
4. Метод index(): Цей метод обробляє запити на головну сторінку. Він 
викликає метод findByRole() у UserRepository для отримання списку майстрів та 
метод findServicesByNumber() у ServicesRepository для отримання списку послуг. 
Після отримання цих даних він передає їх у шаблон homepage.html.twig разом з 
відповідними змінними. 
5. Анотація #[Route('/', name: 'home')]: Ця анотація вказує Symfony, 
яку URL-адресу повинен обробляти цей метод. У цьому випадку, метод index() 
обробляє запити на головну сторінку, яка має шлях /. 
6. Повернення відповіді: Метод index() повертає відповідь, 
використовуючи метод render() для відображення шаблону homepage.html.twig 
разом з переданими даними. 
7. Шаблон Twig: В останньому рядку метод render() вказує Symfony на 
те, який шаблон Twig має бути використаний для відображення сторінки. 
Для створення гнучкого та масштабованого веб-додатку за допомогою 
Symfony, одним з критичних кроків є правильне створення шаблонів, які 
дозволяють відокремлювати логіку додатку від його представлення. Twig, який 
є могутнім шаблонізатором Symfony, надає розробникам потужний інструмент 
для цього завдання. Потрібно створити с початку базовий шаблон від якого 
будуть наслідуватися всі інші шаблони 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
54 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <title>{% block title %}Golden Epil{% endblock %}</title> 
    <link rel="icon" 
          href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 
viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>"> 
    {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #} 
    {% block stylesheets %} 
        {{ encore_entry_link_tags('app') }} 
    {% endblock %} 
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" 
rel="stylesheet" 
          integrity="sha384-
QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" 
crossorigin="anonymous"> 
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-
bundle.min.css"/> 
    {% block javascripts %} 
        {{ encore_entry_script_tags('app') }} 
    {% endblock %} 
</head> 
{% block header %} 
    {% include '/components/header.html.twig' %} 
{% endblock %} 
 
 
<body> 
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> 
{% block body %}{% endblock %} 
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" 
integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" 
crossorigin="anonymous"></script> 
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" 
integrity="sha384-0pUGZvbkm6XF6gxjEnlmuGrJXVbNuzT9qBBavbLwCsOGabYfZo0T0to5eqruptLy" 
crossorigin="anonymous"></script> 
</body> 
<script src="{{ asset('js/header.js') }}"></script> 
<!-- Swiper JS --> 
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script> 
<script src="https://kit.fontawesome.com/58c77c10d4.js" crossorigin="anonymous"></script> 
<!-- Initialize Swiper --> 
<script src="{{ asset('js/swiper.js') }}"></script> 
{% block footer %} 
    {% include '/components/footer.html.twig' %} 
{% endblock %} 
</html> 
Рисунок 3.18 Базовий шаблон 
 
Тепер потрібно в шаблонах наслідувати базовий шаблон, по цьому 
принципу й було створено головну сторінку, ось частина коду головної сторінки: 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
55 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
{% extends 'base.html.twig' %} 
 
     {% block body %} 
         <link href="{{ asset('css/homepage.css') }}" rel="stylesheet"/> 
 
         <div class=" homepage_banner position-relative overflow-hidden p-3 p-md-5  text-
center "> 
             <video autoplay loop muted> 
                 <source src="{{ asset('video/home_banner.mp4') }}" type="video/mp4"> 
             </video> 
 
             <div class="col-md-6 p-lg-5 mx-auto  banner_text"> 
                 <h1 class="display-3 fw-bold text-white ">Найкращий салон</h1> 
                 <h3 class="fw-normal mb-3 text-white">Епіляції манікюру та педікюру</h3> 
             </div> 
 
… 
{% endblock %} 
 
Рисунок 3.19 Частина коду головної сторінки яка демонструє наслідування 
від базового шаблонк 
 
В результаті тепер при переході на головну сторінку отримуємо, сторінку як 
точно співпадає з макетом: 
 
Рисунок 3.20 Головна сторінка 
     ЧДТУ 242280.003 ПЗ Лист 
      
56 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
3.6 Створення сервісів сторінок 
 
Важливим аспектом написання якісного програмного коду є дотримання 
стандартів рефакторингу, зокрема PSR (PHP Standard Recommendations), які 
встановлюють засади форматування та структурування коду. Це допомагає 
забезпечити високу читабельність та підтримку коду в майбутньому. Оскільки 
PHP не підтримує сувору типізацію, введення строгих типів даних в код може 
значно знизити ризик випадкових помилок через неправильне використання 
змінних. 
Для покращення архітектури першого контролера в проекті, 
рекомендується використовувати підхід Dependency Injection (DI), який є 
ключовою особливістю Symfony. Це дозволяє забезпечити більш чисту та 
модульну структуру, автоматично інжектуючи залежності у компоненти через 
конструктори класів або сетери, що підвищує гнучкість та тестованість коду. 
Symfony Flex, ще одна потужна бібліотека у вашому арсеналі, спрощує 
управління залежностями та конфігураціями, автоматично налаштовуючи 
більшість компонентів за вас. Це значно знижує кількість ручних налаштувань і 
дозволяє розробникам зосередитися на більш важливих аспектах розробки 
додатку. 
Такий підхід не лише оптимізує розробку, але й забезпечує легке 
впровадження і масштабування проекту в майбутньому, відкриваючи шлях для 
інтеграції нових технологій та покращень без значних затрат часу на 
переписування існуючого коду. 
Щоб створити сервіси потрібно створити папку з ім’ям «Service» : 
 
Рисунок 3.21 Папка сервісів  
     ЧДТУ 242280.003 ПЗ Лист 
      
57 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Далі створюємо хендлер котрий буде відповідати за обробку даних які 
приходять до контролеру на головну сторінку: 
<?php 
declare(strict_types=1); 
 
namespace App\Service; 
 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
 
class HomeHandler 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
 
    public function __construct(UserRepository $userRepository, ServicesRepository 
$servicesRepository) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
    } 
 
    public function __invoke(): array 
    { 
        $masters = $this->userRepository->findByRole('ROLE_MASTER'); 
        $services = $this->servicesRepository->findServicesByNumber(5); 
        return [ 
            'masters' => $masters, 
            'services' => $services, 
        ]; 
 
    } 
} 
Рисунок 3.22 Хендлер головного контролеру 
 
Тепер викликаємо цей клас в контролері: 
#[Route('/', name: 'home')] 
public function index(HomeHandler $handler) 
{ 
    $context = $handler(); 
    return $this->render('homepage.html.twig',$context); 
} 
 Рисунок 3.23 Винклик хендлеру в головному контролері 
 
Хендлер HomeHandler є прикладом ефективного використання принципів 
SOLID, зокрема принципу єдиної відповідальності. Цей підхід дозволяє 
зосередити всю логіку, пов'язану з обробкою даних для головної сторінки, у 
одному місці, що сприяє кращій організації коду і спрощує його подальше 
тестування та обслуговування. 
У даному випадку, HomeHandler відповідає за отримання даних про 
майстрів та послуги з відповідних репозиторіїв. Використання dependency 
injection для внесення репозиторіїв UserRepository та ServicesRepository 
     ЧДТУ 242280.003 ПЗ Лист 
      
58 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
забезпечує високий рівень декуплінгу та залежностей між компонентами, що 
дозволяє легко змінювати або замінювати компоненти без великих втрат у 
функціональності. 
Код в контролері також демонструє гарну практику: він делегує всю 
бізнес-логіку класу-хендлеру, зосереджуючись лише на обробці HTTP запитів і 
відправці відповідей. Це сприяє створенню тонкого контролера, який легко 
читається та підтримується. 
Застосування цього підходу не тільки покращує структуру коду, але й 
підвищує його модульність. Такий код легше розширювати та адаптувати під 
нові вимоги, оскільки кожен компонент виконує лише визначені функції і 
мінімально взаємодіє з іншими частинами системи. Це дозволяє швидко 
реагувати на зміни у бізнес-процесах без необхідності переписування великих 
блоків коду. 
 
3.7 Створення логіну та реєстрації на сайті 
 
Фреймворк надає документацію щодо правильного створення форми 
логіну та його контролера. На основі цього створено контролер в проекті:  
#[Route('/login', name: 'app_login')] 
public function index(AuthenticationUtils $authenticationUtils): Response 
{ 
    $error = $authenticationUtils->getLastAuthenticationError(); 
    $lastUsername = $authenticationUtils->getLastUsername(); 
    return $this->render('login/index.html.twig', [ 
        'controller_name' => 'LoginLogoutController', 
        'last_username' => $lastUsername, 
        'error' => $error,]);} 
Рисунок 3.24 Контролер логіну 
 
Також за документацією створюємо форму запису. Тепер юзер зможе провести 
логін та побачити свою сесію в Profile Bundle: 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
59 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
<section class="register_section "> 
    <div class="container py-5 h-100"> 
        <div class="row"> 
            <div class="col d-flex justify-content-center align-items-center"> 
                <div class="card card-registration my-4 d-flex "> 
                    <div class="row "> 
                        <div class="col"> 
                            <img src="{{ asset('images/login_banner.jpg') }}" 
                                 alt="Sample photo" class=" register_banner" 
                            /> 
                        </div> 
                        <div class="col d-flex justify-content-center"> 
                            <div class="register_card card-body text-black d-flex 
justify-content-center align-items-center flex-column"> 
                                <h3 class="mb-5 text-center">З поверненням:</h3> 
 
                                <div class="row w-100"> 
                                    <form method="post" action="{{ path('app_login') }}"> 
                                        <div class="form-group pb-4"> 
                                            <input class="form-control register_input" 
                                                   type="email" name="_username" 
id="username" placeholder="Email" 
                                                   required> 
                                        </div> 
                                        <div class="form-group pb-4"> 
                                            <input class="form-control register_input" 
                                                   type="password" name="_password" 
id="password" 
                                                   placeholder="Пароль" required> 
                                        </div> 
                                        <button class="form-control register_btn" 
type="submit"> Вхід</button> 
                                        <p class="text-center">Ще не маєте аккаунт? <br> 
                                            <a class="register_url" href="{{ 
path('register_page') }}">Зареєструватися</a> 
                                            </br> 
                                        </p> 
                                    </form> 
                                </div> 
 
                                {% if error %} 
                                    <div>{{ error.messageKey|trans(error.messageData, 
'security') }}</div> 
                                {% endif %} 
 
                            </div> 
                        </div> 
                    </div> 
                </div> 
            </div> 
        </div> 
    </div> 
</section> 
Рисунок 3.25  Форма логіну шаблон 
 
     ЧДТУ 242280.003 ПЗ Лист 
      
60 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
 
 Рисунок 3.26  Форма логіну 
 
Тепер перейдемо до реєстрації, для цього створимо контролер для обробки 
POST запиту: 
$user = new User(); 
$userDetails = new UserDetails(); 
$user->setEmail($request->get('email')); 
$user->setFirstname($request->get('firstname')); 
$user->setLastname($request->get('lastname')); 
$userDetails->setUser($user); 
$userDetails->setTelegram($request->get('telegram') ?? null); 
$userDetails->setTelegramChatId($request->get('telegramChatId') ?? ''); 
$user->setUserDetails($userDetails); 
$user->setPhone($request->get('phone')); 
$user->setUserDetails(null); 
$user->setRoles((array)'ROLE_USER'); 
$user->setLogo('logo.png'); 
$plaintextPassword = ($request->get('confirm_password')); 
$hashedPassword = $this->passwordHasher->hashPassword($user,$plaintextPassword); 
$user->setPassword($hashedPassword); 
$this->entityManager->persist($userDetails); 
$this->entityManager->persist($user); 
$this->entityManager->flush(); 
if ($user->getId()) { if ($userDetails->getTelegramChatId()) { $this-> 
telegramBotController ->sendRegisterMessage($user, $userDetails->getTelegramChatId());} 
    return new JsonResponse(['success' => true, 'message' => 'Ви успішно зареєстровані 
!']);} else {return new JsonResponse(['success' => false, 'message' => 'Помилка, 
спробуйте будь ласка знову пізніше']);} 
Рисунок 3.27  Код обробки реєстрації  
     ЧДТУ 242280.003 ПЗ Лист 
      
61 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Перейдемо до js скрипту для цієї сторінки: 
$(document).ready(function () { 
    const formInputs = document.querySelectorAll('.form-control.register_input'); 
    const registerButton = document.querySelector('.register_btn'); 
    const passwordInput = document.getElementById('password'); 
    const confirmPasswordInput = document.getElementById('confirm_password'); 
    const telegramButtonModal = document.getElementById('telegram_btn_modal'); 
    function validateInput(input, pattern, isOptional = false) { 
        const value = input.value.trim(); 
        const feedbackElement = input.nextElementSibling; 
        const isValid = value === '' ? isOptional : pattern.test(value); 
 
... 
    function getPatternForInput(input) { 
        switch (input.id) { 
            case 'email': 
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 
            case 'firstname': 
            case 'lastname': 
                return /^[a-zA-Zа-яА-ЯіїІЇ]+$/; 
            case 'phone': 
                return /^\+?\d{10,15}$/; 
            case 'telegram': 
                return /^https?:\/\/t\.me\/[a-zA-Z0-9_]+$/; 
            case 'password': 
            case 'confirm_password': 
                return /.+/; 
            default: 
                return /.*/;}} 
    $('#telegram_btn_modal').click( function (event) { 
        let telegramUrl = $('#telegram').val(); 
        let username = telegramUrl.match(/[^\/]+$/)[0]; 
        $.ajax({ 
            type: 'GET', 
            url: 'http://diplom/waiting_telegram_chat_id', 
            data: {'userName': username}, 
            success: function (response) { 
                $('#telegramChatId').val(response); 
                $('#chatIdUserFromBot').text("Ваш chat id: "+response); 
            }, 
            error: function (xhr, status, error) { 
            }});}); 
    $('form_submit_btn').click(function (event) { 
        event.preventDefault(); 
        if (registerButton.disabled) { 
            alert('Будь ласка, заповніть всі обов\'язкові поля правильно і переконайтеся, 
що паролі збігаються перед відправкою.'); 
            return;} 
        var formData = $(this).serialize(); 
        $.ajax({ 
            type: 'POST', 
            url: $(this).attr('action'), 
            data: formData, 
            success: function (response) { 
                $('.alert-success').css('display', 'block'); 
                $('.alert-success').text('Ви успішно зареєстровані!'); 
                window.history.back(); 
            }, 
            error: function (xhr, status, error) { 
                $('.alert-danger').css('display', 'block'); 
                $('.alert-danger').text('Помилка, спробуйте будь ласка знову 
пізніше!');}});});}); 
Рисунок 3.28 Скрипт для сторінки реєстрації з валідацією 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
62 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Цей скрипт виконує функцію валідації, а також перевіряє звернення до 
телеграм боту, якщо юзер, що звертається до боту має таке ж ім’я саме яке ввів 
юзер, то фіксується та здберігається чат ід юзера для подальшого надсилання 
повідомлень. 
В результаті отримано наступну форму: 
 
Рисунок 3.29 Форма реєстрації 
 
3.8 Symfony Security 
  
Безпека є критично важливим аспектом при роботі з даними користувачів, 
особливо в сучасному цифровому світі. У фреймворку Symfony для забезпечення 
безпеки застосунків існує комплексна бібліотека Symfony Security, яка об'єднує 
різноманітні компоненти та стратегії захисту. Ця бібліотека включає засоби для 
     ЧДТУ 242280.003 ПЗ Лист 
      
63 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
аутентифікації, авторизації, захисту від загальних уразливостей, таких як CSRF 
(Cross-Site Request Forgery), XSS (Cross-Site Scripting) і SQL ін'єкцій. 
Особливо корисними є її можливості для розробки власних правил 
доступу, які можна налаштувати відповідно до конкретних потреб застосунку. 
Крім того, Symfony Security дозволяє легко інтегрувати різноманітні способи 
аутентифікації, включаючи OAuth, HTTP Basic, HTTP Digest та форм-базовану 
аутентифікацію. Це робить її надзвичайно гнучкою та масштабованою для будь-
якого проекту. 
Додатково, ця бібліотека пропонує розширені можливості для кастомізації 
захисту, дозволяючи розробникам детально налаштовувати поведінку системи  
безпеки на основі конкретних вимог бізнесу та можливих ризиків. 
Наприклад, можна налаштувати керування ролями користувачів, доступ до 
ресурсів та обробку помилок безпеки, що допомагає збільшити загальний рівень 
захищеності застосунку. 
Створення сутності користувача в проекті на Symfony має свої 
особливості, які зумовлені необхідністю інтеграції з компонентами Symfony 
Security. Сутність User визначає основні атрибути та поведінку, які є 
необхідними для аутентифікації та авторизації користувачів у системі. Її 
визначення включає поля для зберігання імені, прізвища, електронної пошти, 
телефону, ролей та пароля. Важливо відзначити, що дані про пароль зберігаються 
в хешованому вигляді для забезпечення безпеки. Сутність юзера в проекті має 
наступний вигляд: 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
64 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
<?php 
 
// src/Entity/User.php 
namespace App\Entity; 
 
use App\Repository\UserRepository; 
use Doctrine\ORM\Mapping as ORM; 
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; 
use Symfony\Component\Security\Core\User\UserInterface; 
use Doctrine\Common\Collections\Collection; 
 
#[ORM\Entity(repositoryClass: UserRepository::class)] 
class User implements UserInterface, PasswordAuthenticatedUserInterface 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private int $id; 
    #[ORM\Column(type: 'string', length: 180, unique: true)] 
    private ?string $email; 
    #[ORM\Column(type: 'string')] 
    private string $firstname; 
    #[ORM\Column(type: 'string')] 
    private string $lastname; 
    #[ORM\Column(type: 'string')] 
    private string $phone; 
    #[ORM\Column(type: 'json')] 
    private array $roles = []; 
    #[ORM\Column(type: 'string')] 
    private string $password; 
    #[ORM\Column(type: 'string')] 
    private string $logo; 
    #[ORM\OneToOne(targetEntity: UserDetails::class, mappedBy: "user", cascade: 
["persist"])] 
    private ?UserDetails $userDetails = null; 
Рисунок 3.30 Сутність юзера 
 
Клас User реалізує інтерфейси UserInterface та  
PasswordAuthenticatedUserInterface, що надає Symfony необхідні хуки для 
реалізації функціоналу безпеки. Це дозволяє фреймворку використовувати 
сутність користувача для аутентифікації та ведення сесій, забезпечуючи цим 
високий рівень інтегрованості та безпеки системи. Зокрема, використання 
хешування паролів є критично важливим для захисту даних користувачів від 
несанкціонованого доступу. 
Далі необхідно створити репозиторій: 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
65 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
<?php 
 
namespace App\Repository; 
 
use App\Entity\User; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
 
class UserRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, User::class); 
    } 
    public function findByRole($role) 
    { 
        $qb = $this->createQueryBuilder('u'); 
 
        return $qb 
            ->where('u.roles LIKE :role') 
            ->setParameter('role', '%"'.$role.'"%') 
            ->getQuery() 
            ->getResult(); 
    } 
} 
Рисунок 3.31 Репозиторій сутності юзера 
 
Для забезпечення безпеки даних користувачів, особливо при роботі з 
паролями, Symfony пропонує розширені можливості налаштування хешування 
паролів через конфігураційний файл security.yaml. Ця конфігурація дозволяє 
задати параметри хешування, використовувати різні способи аутентифікації та 
контролювати доступ до різних частин додатку. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
66 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
security: 
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-
passwords 
    password_hashers: 
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' 
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider 
    providers: 
        users_in_memory: { memory: null } 
        app_user_provider: 
            entity: 
                class: App\Entity\User 
                property: email 
    firewalls: 
        dev: 
            pattern: ^/(_(profiler|wdt)|css|images|js)/ 
            security: false 
        main: 
            lazy: true 
            provider: app_user_provider 
            logout: 
                path: app_logout 
            form_login: 
                login_path: app_login 
                check_path: app_login 
                always_use_default_target_path: false 
 
    role_hierarchy: 
        ROLE_USER: ROLE_USER 
        ROLE_ADMIN: ROLE_ADMIN 
        ROLE_SUPER_ADMIN: [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ] 
    access_control: 
        - { path: ^/master_cabinet, roles: [ ROLE_ADMIN, ROLE_MASTER ] } 
        - { path: ^/user_cabinet, roles: [ ROLE_ADMIN, ROLE_USER] } 
 
when@test: 
    security: 
        password_hashers: 
            Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 
                algorithm: auto 
                cost: 4 # Lowest possible value for bcrypt 
                time_cost: 3 # Lowest possible value for argon 
                memory_cost: 10 # Lowest possible value for argon 
Рисунок 3.32 Налаштування безпеки 
 
Висновки до розділу 3: 
 
У цьому розділі детально описано процес розробки та налаштування сайту 
"Golden Epil". Розглянуто його структуру, проведено інструкції з локального 
розгортання та пояснено роботу з базою даних. Додатково докладно розглянуто 
використання Symfony та Sonata, включаючи інструкції щодо розгортання 
Symfony Sonata та використання Symfony Doctrine. Також надано приклади 
верстки з використанням шаблонів та пояснено, як використовувати Twig для 
зручного розміщення даних. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
67 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
ВИСНОВКИ 
 
 
У рамках виконання кваліфікаційної роботи бакалавра  було успішно 
розроблено веб-орієнтовану інформаційну систему для салону краси "Golden 
Epil", що включає ряд функціональних модулів, здатних значно покращити 
взаємодію з клієнтами та оптимізувати внутрішні процеси установи. Проект було 
реалізовано на базі фреймворку Symfony, що дозволило забезпечити високий 
рівень безпеки, гнучкість та масштабованість системи. 
Значна увага в роботі приділялась підвищенню юзабіліті: інтуїтивно 
зрозумілий інтерфейс, зручна навігація, адаптивність дизайну до різних типів 
пристроїв, що сприяє зручності користувачів і, як наслідок, підвищенню їх 
лояльності. Також було втілено ряд особливостей, таких як система запису на 
процедури, кабінети для користувачів і майстрів, що дозволяють керувати 
адміністраційними процесами і клієнтською базою. 
Протягом роботи над проектом були ефективно використані сучасні 
інструменти розробки та методики програмування. Використання системи 
контролю версій Git дозволило організувати продуктивну командну роботу. 
Реалізація проекту через TDD (Test Driven Development) і використання PHPUnit 
забезпечили високу надійність і стабільність роботи системи. 
В проекті викорстанний рефакторинг задля того щоб забезпечити 
подальше комфортне розширення функціоналу та можливе запрошення інших 
спеціалістів. 
Результати реалізації проекту показали, що створена система відповідає 
всім сучасним вимогам до веб-додатків і має значний потенціал для подальшого 
розвитку.  
  
     ЧДТУ 242280.003 ПЗ Лист 
      
68 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
Перспективи розвитку проекту включають в себе розширення 
функціональності системи, інтеграцію з більшою кількістю зовнішніх сервісів, а 
також подальше вдосконалення інтерфейсу та користувацького досвіду. 
В ході виконання кваліфікаційної роботи бакалавра виконані наступні 
завдання: 
 Проаналізовано існуючі рішення 
 Спроектовано архітектуру та дизайн сайту 
 Реалізовано функціонал сайту 
 Створено кабінети користувача та майстра 
 Створено логін та реєстрацію 
 Забезпечено безпеку 
 Написано телеграм бот  
 Створено базу та підключення до неї  
 Реалізовано можливість запису на процедуру та правки запису 
 Протестована веб-платформа 
Таким чином досягнута мета кваліфікаційної роботи бакалавра: розробка 
веб-ресурсу компанії з надання послуг краси та здоров'я з використанням 
сучасних технологій створення WEB-додатків. 
Даний проект не тільки вирішує важливі задачі для салону краси "Golden 
Epil", але й відкриває шляхи для його подальшого розвитку, що зможе сприяти 
зростанню бізнесу та підвищенню задоволеності клієнтів. 
  
     ЧДТУ 242280.003 ПЗ Лист 
      
69 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
ВИКОРИСТАНІ ДЖЕРЕЛА 
 
 
1. Сучасні тенденції в індустрії краси: аналіз. URL: 
https://trafft.com/salon-industry-trends/875о (дата звернення: 14.03.2024). 
2. Звіт з аналізу ринку салонів краси від Grand View Research. URL: 
https://www.grandviewresearch.com/industry-analysis/beauty-salon-market-report 
(дата звернення: 20.03.2024). 
3. Офіційна документація Symfony. URL: 
https://symfony.com/doc/current/index.html (дата звернення: 26.03.2024). 
4. Налаштування Symfony. URL: 
https://symfony.com/doc/current/setup.html (дата звернення: 01.04.2024). 
5. Керування контролерами в Symfony. URL: 
https://symfony.com/doc/current/controller.html (дата звернення: 05.04.2024). 
6. Вхід в систему за допомогою Symfony Security. URL: 
https://symfony.com/doc/current/security.html#the-user (дата звернення: 
10.04.2024). 
7. Авторизація через форму в Symfony. URL: 
https://symfony.com/doc/current/security.html#form-login (дата звернення: 
15.04.2024). 
8. Документація Sonata Project. URL: https://docs.sonata-
project.org/en/master/ (дата звернення: 20.04.2024). 
9. Інсталяція Sonata Admin Bundle. URL: https://docs.sonata-
project.org/projects/SonataAdminBundle/en/4.x/getting_started/installation/ (дата 
звернення: 25.04.2024). 
10. Робота з переліком даних у Sonata Admin Bundle. URL: 
https://docs.sonata-project.org /projects / SonataAdminBundle/en/4.x/getting_started/ 
the_list_view/ (дата звернення: 30.04.2024). 
11. Огляд індустрії краси на trafft.com. URL: https://trafft.com/salon-
industry-trends/ (дата звернення: 05.05.2024). 
     ЧДТУ 242280.003 ПЗ Лист 
      
70 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
12. Аналіз ринку салонів краси на сайті Grand View Research. URL: 
https://www.grandviewresearch.com/industry-analysis/beauty-salon-market-report 
(дата звернення: 10.05.2024). 
13. API Telegram: огляд. URL: https://core.telegram.org/api (дата 
звернення: 15.05.2024). 
14. Методи API Telegram. URL: https://core.telegram.org/api#api-methods 
(дата звернення: 20.05.2024). 
15. Конфігурація бази даних у Symfony. URL: 
https://symfony.com/doc/current/doctrine.html (дата звернення: 16.03.2024). 
16. Створення форм у Symfony. URL: 
https://symfony.com/doc/current/forms.html (дата звернення: 18.03.2024). 
17. Маршрутизація у Symfony. URL: 
https://symfony.com/doc/current/routing.html (дата звернення: 22.03.2024). 
18. Кешування у Symfony. URL: 
https://symfony.com/doc/current/cache.html (дата звернення: 25.03.2024). 
19. Використання компонента Security у Symfony. URL: 
https://symfony.com/doc/current/components/security.html (дата звернення: 
28.03.2024). 
20. Розширення Sonata Admin: налаштування дашборду. URL: 
https://docs.sonata-
project.org/projects/SonataAdminBundle/en/4.x/cookbook/dashboard_customization/ 
(дата звернення: 31.03.2024). 
21. Впровадження міжнародної підтримки у Symfony. URL: 
https://symfony.com/doc/current/translation.html (дата звернення: 04.04.2024). 
22. Управління сесіями у Symfony. URL: 
https://symfony.com/doc/current/session.html (дата звернення: 07.04.2024). 
23. Керування залежностями у Symfony за допомогою компонента 
DependencyInjection. URL: 
https://symfony.com/doc/current/components/dependency_injection.html (дата 
звернення: 10.04.2024). 
     ЧДТУ 242280.003 ПЗ Лист 
      
71 
Зм Арк. № докум. Пiдпис. Дата 
нт
` 
 
24. Принципи роботи Doctrine ORM. URL: https://www.doctrine-
project.org/projects/doctrine-orm/en/latest/index.html (дата звернення: 12.04.2024). 
25. Створення бота для Telegram з використанням PHP. URL: 
https://www.sitepoint.com/telegram-bots-php/ (дата звернення: 14.04.2024). 
26. Настройка інтеграції Sonata MediaBundle. URL: https://docs.sonata-
project.org/projects/SonataMediaBundle/en/4.x/ (дата звернення: 17.04.2024). 
27. Поводження з винятками у Symfony. URL: 
https://symfony.com/doc/current/controller/error_pages.html (дата звернення: 
21.04.2024). 
28. Використання WebSocket у Symfony. URL: 
https://symfony.com/doc/current/websockets.html (дата звернення: 25.04.2024). 
29. Інтеграція реального часу в Telegram ботів. URL: 
https://core.telegram.org/bots/api#making-requests-when-getting-updates (дата 
звернення: 30.04.2024). 
 
     ЧДТУ 242280.003 ПЗ Лист 
      
72 
Зм Арк. № докум. Пiдпис. Дата 
нт
 
 
ДОДАТОК А 
 
 
                                                                    ЗАТВЕРДЖЕНО 
                                                                    Зав.кафедри ІТП 
____________Прокопенко Т.О. 
“_____” _____________2024р. 
 
 
 
 
  Сайт компанії з надання послуг краси та здоров'я  
 
Специфікація 
482 ЧДТУ 242280 – 01 
Листів 2 
 
Н 
 
Розробник                            _____________                     Декен Є.Р. 
Керівник                              _____________                      Рудницький С.В. 
Н. контроль                          _____________                     Рудницький С.В. 
 
                                                                                                                            
 
 
 
 
Черкаси, 2024 
 
482 ЧДТУ 242280 – 01 
Позначення Найменування Примітка 
482 ЧДТУ 242280 – 01 12 Специфікація  
01  
482 ЧДТУ 242280 – 01 12 Текст програми бекенд  
02 
482 ЧДТУ 242280 – 01 12 Макети сайту  
03 
 
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
 
 
ДОДАТОК Б 
 
 
                                                                    ЗАТВЕРДЖЕНО 
                                                                    Зав.кафедри ІТП 
____________Прокопенко Т.О. 
“_____” _____________2024р. 
 
 
 
 
  Сайт компанії з надання послуг краси та здоров'я  
 
Текст програми бекенд 
482 ЧДТУ 242280 – 01 12  
Листів 41 
 
Н 
 
Розробник                            _____________                     Декен Є.Р. 
Керівник                              _____________                      Рудницький С.В. 
Н. контроль                          _____________                     Рудницький С.В. 
 
                                                                                                                            
 
 
 
Черкаси, 2024 
 
СУТНОСТІ 
 
Приклад коду моделі даних Appointment: 
<?php 
 
namespace App\Entity; 
 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: 'App\Repository\AppointmentRepository')] 
#[ORM\Table(name: 'appointments')] 
class Appointment 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private ?int $id = null; 
 
    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'id')] 
    private User $employer; 
 
    #[ORM\ManyToOne(targetEntity: Services::class, inversedBy: 'id')] 
    private ?Services $service=null; 
    #[ORM\Column(name: 'price',type: 'string')] 
    private string $price; 
 
    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'id')] 
    private User $user; 
 
    #[ORM\Column(name: 'create_at', type: 'datetime')] 
    private \DateTime $createAt; 
 
    #[ORM\Column(name: 'time', type: 'datetime')] 
    private \DateTime $time; 
 
    #[ORM\ManyToOne(targetEntity: AppointmentStatus::class, inversedBy: 'id')] 
    private ?AppointmentStatus $status = null; 
 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return User 
     */ 
    public function getUser(): User 
    { 
        return $this->user; 
    } 
    /** 
     * @param User $user 
     */ 
    public function setUser(User $user): void 
    { 
        $this->user = $user; 
    } 
    /** 
     * @return Services 
     */ 
    public function getService(): Services 
    { 
        return $this->service; 
    } 
 
 
 
    /** 
     * @param Services $service 
     */ 
    public function setService(Services $service): void 
    { 
        $this->service = $service; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getCreateAt(): \DateTime 
    { 
        return $this->createAt; 
    } 
    /** 
     * @param \DateTime $createAt 
     */ 
    public function setCreateAt(\DateTime $createAt): void 
    { 
        $this->createAt = $createAt; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getTime(): \DateTime 
    { 
        return $this->time; 
    } 
    /** 
     * @param \DateTime $time 
     */ 
    public function setTime(\DateTime $time): void 
    { 
        $this->time = $time; 
    } 
    /** 
     * @return AppointmentStatus|null 
     */ 
    public function getStatus(): ?AppointmentStatus 
    { 
        return $this->status; 
    } 
    /** 
     * @param AppointmentStatus|null $status 
     */ 
    public function setStatus(?AppointmentStatus $status): void 
    { 
        $this->status = $status; 
    } 
    /** 
     * @return User 
     */ 
    public function getEmployer(): User 
    { 
        return $this->employer; 
    } 
    /** 
     * @param User $employer 
     */ 
    public function setEmployer(User $employer): void 
    { 
        $this->employer = $employer; 
    } 
    /** 
     * @return string 
     */ 
    public function getPrice(): string 
    { 
        return $this->price; 
    } 
    /** 
     * @param string $price 
     */ 
    public function setPrice(string $price): void 
    { 
        $this->price = $price; 
    }} 
 
 
Приклад коду моделі даних AppointmentStatus: 
<?php 
namespace App\Entity; 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: 'App\Repository\AppointmentStatusRepository')] 
#[ORM\Table(name: 'appointments_status')] 
class AppointmentStatus 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private ?int $id = null; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $name = null; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $nameUA = null; 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return string|null 
     */ 
    public function getName(): ?string 
    { 
        return $this->name; 
    } 
    /** 
     * @param string|null $name 
     */ 
    public function setName(?string $name): void 
    { 
        $this->name = $name; 
    } 
    /** 
     * @return string|null 
     */ 
    public function getNameUA(): ?string 
    { 
        return $this->nameUA; 
    } 
    /** 
     * @param string|null $nameUA 
     */ 
    public function setNameUA(?string $nameUA): void 
    { 
        $this->nameUA = $nameUA; 
    }} 
 
Приклад коду моделі даних Courses: 
<?php 
 
namespace App\Entity; 
 
use App\Model\CourseStatus; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\ORM\Mapping as ORM; 
use Doctrine\Common\Collections\Collection; 
use Doctrine\Common\Collections\ArrayCollection; 
#[ORM\Entity(repositoryClass: 'App\Repository\CoursesRepository')] 
#[ORM\Table(name: 'courses')]  
 
 
class Courses 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private ?int $id = null; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $kourseName; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $courseType; 
    #[ORM\OneToMany(targetEntity: CoursesInfo::class, mappedBy: 'courses')] 
    private ?Collection $coursesInfo = null; 
 
    #[ORM\Column(type: 'text')] 
    private ?string $description = null; 
 
    #[ORM\Column(name: 'start_time', type: 'datetime')] 
    private \DateTime $startTime; 
 
    #[ORM\Column(name: 'end_time', type: 'datetime')] 
    private \DateTime $endTime; 
 
    #[ORM\Column(type: 'string')] 
    private $status; 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return mixed 
     */ 
    public function getCourseType() 
    { 
        return $this->courseType; 
    } 
    /** 
     * @param mixed $courseType 
     */ 
    public function setCourseType($courseType): void 
    { 
        $this->courseType = $courseType; 
    } 
    /** 
     * @return mixed 
     */ 
    public function getKourseName() 
    { 
        return $this->kourseName; 
    } 
    /** 
     * @param mixed $kourseName 
     */ 
    public function setKourseName($kourseName): void 
    { 
        $this->kourseName = $kourseName; 
    } 
    /** 
     * @return Collection|null 
     */ 
    public function getCoursesInfo(): ?Collection 
    { 
        return $this->coursesInfo; 
    } 
    /** 
     * @param Collection|null $coursesInfo 
     */ 
    public function setCoursesInfo(?Collection $coursesInfo): void 
 
 
    { 
        $this->coursesInfo = $coursesInfo; 
    } 
    /** 
     * @return string|null 
     */ 
    public function getDescription(): ?string 
    { 
        return $this->description; 
    } 
    /** 
     * @param string|null $description 
     */ 
    public function setDescription(?string $description): void 
    { 
        $this->description = $description; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getStartTime(): \DateTime 
    { 
        return $this->startTime; 
    } 
    /** 
     * @param \DateTime $startTime 
     */ 
    public function setStartTime(\DateTime $startTime): void 
    { 
        $this->startTime = $startTime; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getEndTime(): \DateTime 
    { 
        return $this->endTime; 
    } 
    /** 
     * @param \DateTime $endTime 
     */ 
    public function setEndTime(\DateTime $endTime): void 
    { 
        $this->endTime = $endTime; 
    } 
    /** 
     * @return mixed 
     */ 
    public function getStatus() 
    { 
        return $this->status; 
    } 
    /** 
     * @param mixed $status 
     */ 
    public function setStatus($status): void 
    { 
        $this->status = $status; 
    }} 
 
Приклад коду моделі даних CoursesInfo: 
<?php 
 
namespace App\Entity; 
 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: 'App\Repository\CoursesInfoRepository')] 
#[ORM\Table(name: 'courses_info')] 
class CoursesInfo 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private ?int $id = null;  
 
    #[ORM\ManyToOne(targetEntity: Courses::class, inversedBy: 'courses')] 
    #[ORM\JoinColumn(name: 'courses_id', referencedColumnName: 'id')] 
    private ?Courses $courses = null; 
    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'id')] 
    private User $instructor; 
    #[ORM\Column(type:'text')] 
    private?string $firstParagraph=''; 
    #[ORM\Column(type:'json')] 
    private?array $skills = []; 
    #[ORM\Column(type:'text')] 
    private?string $endParagraph; 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return Courses|null 
     */ 
    public function getCourses(): ?Courses 
    { 
        return $this->courses; 
    } 
    /** 
     * @param Courses|null $courses 
     */ 
    public function setCourses(?Courses $courses): void 
    { 
        $this->courses = $courses; 
    } 
    /** 
     * @return User 
     */ 
    public function getInstructor(): User 
    { 
        return $this->instructor; 
    } 
    /** 
     * @param User $instructor 
     */ 
    public function setInstructor(User $instructor): void 
    { 
        $this->instructor = $instructor; 
    } 
    /** 
     * @return string|null 
     */ 
    public function getFirstParagraph(): ?string 
    { 
        return $this->firstParagraph; 
    } 
    /** 
     * @param string|null $firstParagraph 
     */ 
    public function setFirstParagraph(?string $firstParagraph): void 
    { 
        $this->firstParagraph = $firstParagraph; 
    } 
    /** 
     * @return array|null 
     */ 
    public function getSkills(): ?array 
    { 
        return $this->skills; 
    } 
    /** 
     * @param array|null $skills 
     */ 
    public function setSkills(?array $skills): void 
    { 
        $this->skills = $skills;  
 
 
    } 
    /** 
     * @return string|null 
     */ 
    public function getEndParagraph(): ?string 
    { 
        return $this->endParagraph; 
    } 
    /** 
     * @param string|null $endParagraph 
     */ 
    public function setEndParagraph(?string $endParagraph): void 
    { 
        $this->endParagraph = $endParagraph; 
    }} 
 
Приклад коду моделі даних CoursesStudent: 
<?php 
 
namespace App\Entity; 
 
use App\Entity\Courses; 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: 'App\Repository\CoursesStudentRepository')] 
#[ORM\Table(name: 'courses_student')] 
class CoursesStudent 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private ?int $id = null; 
    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'id')] 
    private User $user; 
 
    #[ORM\ManyToOne(targetEntity: Courses::class, inversedBy: 'id')] 
    private ?Courses $kourse; 
 
    #[ORM\Column(name: 'start_time', type: 'datetime')] 
    private \DateTime $startTime; 
 
    #[ORM\Column(name: 'end_time', type: 'datetime')] 
    private \DateTime $endTime; 
 
    #[ORM\Column(type: 'string')] 
    private string $status; 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return User 
     */ 
    public function getUser(): User 
    { 
        return $this->user; 
    } 
    /** 
     * @param User $user 
     */ 
    public function setUser(User $user): void 
    { 
        $this->user = $user; 
    } 
    /** 
 
 
     * @return \App\Entity\Courses|null 
     */ 
    public function getKourse(): ?\App\Entity\Courses 
    { 
        return $this->kourse; 
    } 
    /** 
     * @param \App\Entity\Courses|null $kourse 
     */ 
    public function setKourse(?\App\Entity\Courses $kourse): void 
    { 
        $this->kourse = $kourse; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getStartTime(): \DateTime 
    { 
        return $this->startTime; 
    } 
    /** 
     * @param \DateTime $startTime 
     */ 
    public function setStartTime(\DateTime $startTime): void 
    { 
        $this->startTime = $startTime; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getEndTime(): \DateTime 
    { 
        return $this->endTime; 
    } 
    /** 
     * @param \DateTime $endTime 
     */ 
    public function setEndTime(\DateTime $endTime): void 
    { 
        $this->endTime = $endTime; 
    } 
    /** 
     * @return string 
     */ 
    public function getStatus(): string 
    { 
        return $this->status; 
    } 
    /** 
     * @param string $status 
     */ 
    public function setStatus(string $status): void 
    { 
        $this->status = $status; 
    }} 
 
Приклад коду моделі даних FormMessages: 
<?php 
 
namespace App\Entity; 
 
use Doctrine\ORM\Mapping as ORM; 
#[ORM\Entity(repositoryClass: 'App\Repository\FormMessagesRepository')] 
#[ORM\Table(name: 'form_messages')] 
class FormMessages 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private int $id; 
    #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'id')] 
    private ?User $user = null; 
    #[ORM\Column(type: 'string', length: 255)] 
    private string $firstName; 
    #[ORM\Column(type: 'string', length: 255)] 
    private string $lastName;  
 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private string $email; 
    #[ORM\Column(type: 'text')] 
    private string $message; 
    #[ORM\Column(type: 'datetime')] 
    private \DateTime $createdAt; 
    /** 
     * @return int 
     */ 
    public function getId(): int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int $id 
     */ 
    public function setId(int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return User|null 
     */ 
    public function getUser(): ?User 
    { 
        return $this->user; 
    } 
    /** 
     * @param User|null $user 
     */ 
    public function setUser(?User $user): void 
    { 
        $this->user = $user; 
    } 
    /** 
     * @return string 
     */ 
    public function getFirstName(): string 
    { 
        return $this->firstName; 
    } 
    /** 
     * @param string $firstName 
     */ 
    public function setFirstName(string $firstName): void 
    { 
        $this->firstName = $firstName; 
    } 
    /** 
     * @return string 
     */ 
    public function getLastName(): string 
    { 
        return $this->lastName; 
    } 
    /** 
     * @param string $lastName 
     */ 
    public function setLastName(string $lastName): void 
    { 
        $this->lastName = $lastName; 
    } 
    /** 
     * @return string 
     */ 
    public function getEmail(): string 
    { 
        return $this->email; 
    } 
    /** 
     * @param string $email 
     */ 
    public function setEmail(string $email): void 
    { 
        $this->email = $email; 
    } 
    /** 
     * @return string 
     */ 
 
 
    public function getMessage(): string 
    { 
        return $this->message; 
    } 
    /** 
     * @param string $message 
     */ 
    public function setMessage(string $message): void 
    { 
        $this->message = $message; 
    } 
    /** 
     * @return \DateTime 
     */ 
    public function getCreatedAt(): \DateTime 
    { 
        return $this->createdAt; 
    } 
    /** 
     * @param \DateTime $createdAt 
     */ 
    public function setCreatedAt(\DateTime $createdAt): void 
    { 
        $this->createdAt = $createdAt; 
    }} 
 
Приклад коду моделі даних Services: 
<?php 
 
namespace App\Entity; 
 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: 'App\Repository\ServicesRepository')] 
#[ORM\Table(name: 'services')] 
class Services 
{ 
    #[ORM\Column(type: 'integer')] 
    #[ORM\Id] 
    #[ORM\GeneratedValue(strategy: 'AUTO')] 
    private ?int $id; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $name; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $description; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $image; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $price; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $category; 
 
    #[ORM\Column(type: 'string', length: 255)] 
    private ?string $type; 
    /** 
     * @return int|null 
     */ 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
    /** 
     * @param int|null $id 
     */ 
    public function setId(?int $id): void 
    { 
        $this->id = $id; 
    } 
    /** 
     * @return string|null 
     */ 
 
 
    public function getName(): ?string 
    { 
        return $this->name; 
    } 
 
    /** 
     * @param string|null $name 
     */ 
    public function setName(?string $name): void 
    { 
        $this->name = $name; 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getDescription(): ?string 
    { 
        return $this->description; 
    } 
 
    /** 
     * @param string|null $description 
     */ 
    public function setDescription(?string $description): void 
    { 
        $this->description = $description; 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getImage(): ?string 
    { 
        return $this->image; 
    } 
 
    /** 
     * @param string|null $image 
     */ 
    public function setImage(?string $image): void 
    { 
        $this->image = $image; 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getPrice(): ?string 
    { 
        return $this->price; 
    } 
 
    /** 
     * @param string|null $price 
     */ 
    public function setPrice(?string $price): void 
    { 
        $this->price = $price; 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getCategory(): ?string 
    { 
        return $this->category; 
    } 
 
    /** 
     * @param string|null $category 
     */ 
    public function setCategory(?string $category): void 
    { 
        $this->category = $category; 
    } 
 
    /** 
     * @return string|null 
 
 
     */ 
    public function getType(): ?string 
    { 
        return $this->type; 
    } 
 
    /** 
     * @param string|null $type 
     */ 
    public function setType(?string $type): void 
    { 
        $this->type = $type; 
    } 
} 
 
Приклад коду моделі даних User: 
<?php 
namespace App\Entity; 
 
use App\Repository\UserRepository; 
use Doctrine\ORM\Mapping as ORM; 
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; 
use Symfony\Component\Security\Core\User\UserInterface; 
use Doctrine\Common\Collections\Collection; 
 
#[ORM\Entity(repositoryClass: UserRepository::class)] 
class User implements UserInterface, PasswordAuthenticatedUserInterface 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private int $id; 
 
    #[ORM\Column(type: 'string', length: 180, unique: true)] 
    private ?string $email; 
 
    #[ORM\Column(type: 'string')] 
    private string $firstname; 
 
    #[ORM\Column(type: 'string')] 
    private string $lastname; 
 
    #[ORM\Column(type: 'string')] 
    private string $phone; 
 
 
    #[ORM\Column(type: 'json')] 
    private array $roles = []; 
 
    #[ORM\Column(type: 'string')] 
    private string $password; 
 
    #[ORM\Column(type: 'string')] 
    private string $logo; 
 
    #[ORM\OneToOne(targetEntity: UserDetails::class, mappedBy: "user", cascade: ["persist"])] 
    private ?UserDetails $userDetails = null; 
 
    public function getId(): ?int 
    { 
        return $this->id; 
    } 
 
    public function getEmail(): ?string 
    { 
        return $this->email; 
    } 
 
    public function setEmail(string $email): self 
    { 
        $this->email = $email; 
 
        return $this; 
    } 
 
 
 
    /** 
     * The public representation of the user (e.g. a username, an email address, etc.) 
     * 
     * @see UserInterface 
     */ 
    public function getUserIdentifier(): string 
    { 
        return (string)$this->email; 
    } 
 
    /** 
     * @see UserInterface 
     */ 
    public function getRoles(): array 
    { 
        $roles = $this->roles; 
        // guarantee every user at least has ROLE_USER 
        $roles[] = 'ROLE_USER'; 
 
        return array_unique($roles); 
    } 
 
    public function setRoles(array $roles): self 
    { 
        $this->roles = $roles; 
 
        return $this; 
    } 
 
    /** 
     * @see PasswordAuthenticatedUserInterface 
     */ 
    public function getPassword(): string 
    { 
        return $this->password; 
    } 
 
    public function setPassword(string $password): self 
    { 
        $this->password = $password; 
 
        return $this; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getFirstname(): string 
    { 
        return $this->firstname; 
    } 
 
    /** 
     * @param string $firstname 
     */ 
    public function setFirstname(string $firstname): void 
    { 
        $this->firstname = $firstname; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getLastname(): string 
    { 
        return $this->lastname; 
    } 
 
    /** 
     * @param string $lastname 
     */ 
    public function setLastname(string $lastname): void 
    { 
        $this->lastname = $lastname; 
    } 
 
    /** 
     * @return string 
     */ 
 
 
    public function getPhone(): string 
    { 
        return $this->phone; 
    } 
 
    /** 
     * @param string $phone 
     */ 
    public function setPhone(string $phone): void 
    { 
        $this->phone = $phone; 
    } 
 
 
    /** 
     * @return string 
     */ 
    public function getLogo(): string 
    { 
        return $this->logo; 
    } 
 
    /** 
     * @param string $logo 
     */ 
    public function setLogo(string $logo): void 
    { 
        $this->logo = $logo; 
    } 
 
    /** 
     * @return UserDetails|null 
     */ 
    public function getUserDetails(): ?UserDetails 
    { 
        return $this->userDetails; 
    } 
 
    /** 
     * @param UserDetails|null $userDetails 
     */ 
    public function setUserDetails(?UserDetails $userDetails): void 
    { 
        $this->userDetails = $userDetails; 
    } 
 
 
 
    /** 
     * @see UserInterface 
     */ 
    public function eraseCredentials(): void 
    { 
        // If you store any temporary, sensitive data on the user, clear it here 
        // $this->plainPassword = null; 
    } 
} 
 
Приклад коду моделі даних UserDetails: 
<?php 
 
namespace App\Entity; 
 
use App\Repository\UserDetailsRepository; 
use Doctrine\ORM\Mapping as ORM; 
 
#[ORM\Entity(repositoryClass: UserDetailsRepository::class)] 
class UserDetails 
{ 
    #[ORM\Id] 
    #[ORM\GeneratedValue] 
    #[ORM\Column(type: 'integer')] 
    private int $id; 
    #[ORM\OneToOne(targetEntity: User::class, inversedBy: "userDetails", cascade: ["persist"])] 
    #[ORM\JoinColumn(name: "user_id", referencedColumnName: "id", nullable: true)] 
    private ?User $user = null;  
 
 
    #[ORM\Column(type: 'string', length: 255, nullable: true)] 
    private ?string $instagram = null; 
 
    #[ORM\Column(type: 'string', nullable: true)] 
    private ?string $telegram = null; 
    #[ORM\Column(type: 'string', nullable: true)] 
    private ?string $telegramChatId = null; 
    #[ORM\Column(type: 'boolean')] 
    private bool $telegramAllowSendMessage = false; 
 
    #[ORM\Column(type: 'string', length: 255, nullable: true)] 
    private ?string $profession = null; 
 
    #[ORM\Column(type: 'json', length: 255, options: ["default" => "{}"])] 
    private mixed $abilitys = []; 
 
    #[ORM\Column(type: 'string', length: 255, nullable: true)] 
    private ?string $city = null; 
 
    /** 
     * @return int 
     */ 
    public function getId(): int 
    { 
        return $this->id; 
    } 
 
    /** 
     * @param int $id 
     */ 
    public function setId(int $id): void 
    { 
        $this->id = $id; 
    } 
 
    /** 
     * @return User|null 
     */ 
    public function getUser(): ?User 
    { 
        return $this->user; 
    } 
 
    /** 
     * @param User|null $user 
     */ 
    public function setUser(?User $user): void 
    { 
        $this->user = $user; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getInstagram(): string 
    { 
        return $this->instagram; 
    } 
 
    /** 
     * @param string $instagram 
     */ 
    public function setInstagram(string $instagram): void 
    { 
        $this->instagram = $instagram; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getTelegram(): string 
    { 
        return $this->telegram; 
    } 
 
    /** 
     * @param string $telegram 
     */ 
    public function setTelegram(string $telegram): void 
 
 
    { 
        $this->telegram = $telegram; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getProfession(): string 
    { 
        return $this->profession; 
    } 
 
    /** 
     * @param string $profession 
     */ 
    public function setProfession(string $profession): void 
    { 
        $this->profession = $profession; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getCity(): string 
    { 
        return $this->city; 
    } 
 
    /** 
     * @param string $city 
     */ 
    public function setCity(string $city): void 
    { 
        $this->city = $city; 
    } 
 
    /** 
     * @return mixed 
     */ 
    public function getAbilitys(): mixed 
    { 
        return $this->abilitys; 
    } 
 
    /** 
     * @param mixed $abilitys 
     */ 
    public function setAbilitys(mixed $abilitys): void 
    { 
        $this->abilitys = $abilitys; 
    } 
 
    /** 
     * @return string 
     */ 
    public function getTelegramChatId(): string 
    { 
        return $this->telegramChatId; 
    } 
 
    /** 
     * @param string $telegramChatId 
     */ 
    public function setTelegramChatId(string $telegramChatId): void 
    { 
        $this->telegramChatId = $telegramChatId; 
    } 
 
    /** 
     * @return bool 
     */ 
    public function isTelegramAllowSendMessage(): bool 
    { 
        return $this->telegramAllowSendMessage; 
    } 
 
    /** 
     * @param bool $telegramAllowSendMessage 
     */ 
 
 
    public function setTelegramAllowSendMessage(bool $telegramAllowSendMessage): void 
    { 
        $this->telegramAllowSendMessage = $telegramAllowSendMessage; 
    } 
 
 
} 
РЕПОЗИТОРІЇ 
 
Приклад коду репозиторію AppointmentRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\Appointment; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class AppointmentRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, Appointment::class); 
    } 
} 
 
Приклад коду репозиторію AppointmentStatusRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\AppointmentStatus; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class AppointmentStatusRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, AppointmentStatus::class); 
    } 
} 
 
Приклад коду репозиторію CoursesInfoRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\CoursesInfo; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class CoursesInfoRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, CoursesInfo::class); 
    } 
} 
 
Приклад коду репозиторію CoursesRepository: 
<?php 
 
namespace App\Repository; 
 
 
 
use App\Entity\Courses; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class CoursesRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, Courses::class); 
    } 
} 
 
Приклад коду репозиторію CoursesStudentRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\CoursesStudent; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class CoursesStudentRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, CoursesStudent::class); 
    } 
} 
 
Приклад коду репозиторію FormMessagesRepository: 
<?php 
 
namespace App\Repository; 
 
use Doctrine\ORM\EntityRepository; 
 
class FormMessagesRepository extends EntityRepository 
{ 
 
} 
 
Приклад коду репозиторію ServicesRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\Services; 
use App\Model\ServicesType; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\ORM\QueryBuilder; 
use Doctrine\Persistence\ManagerRegistry; 
 
class ServicesRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, Services::class); 
    } 
 
    public function findServicesByNumber($number) 
    { 
        $entityManager = $this->getEntityManager(); 
        $queryBuilder = $entityManager->createQueryBuilder(); 
 
        $queryBuilder->select('s') 
            ->from(Services::class, 's') 
            ->andWhere('s.type = :type') 
            ->setParameter('type', ServicesType::SERVICE); 
 
        $services = $queryBuilder->getQuery()->getResult(); 
 
 
        shuffle($services); 
        return array_slice($services, 0, $number); 
    } 
} 
 
Приклад коду репозиторію UserDetailsRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\UserDetails; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
class UserDetailsRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, UserDetails::class); 
    } 
} 
 
Приклад коду репозиторію UserRepository: 
<?php 
 
namespace App\Repository; 
 
use App\Entity\User; 
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 
use Doctrine\ORM\EntityRepository; 
use Doctrine\Persistence\ManagerRegistry; 
 
 
class UserRepository extends ServiceEntityRepository 
{ 
    public function __construct(ManagerRegistry $registry) 
    { 
        parent::__construct($registry, User::class); 
    } 
    public function findByRole($role) 
    { 
        $qb = $this->createQueryBuilder('u'); 
 
        return $qb 
            ->where('u.roles LIKE :role') 
            ->setParameter('role', '%"'.$role.'"%') 
            ->getQuery() 
            ->getResult(); 
    } 
} 
 
 
КОНТРОЛЕРИ  
 
Приклад коду репозиторію MainController: 
<?php 
 
namespace App\Controller; 
 
use App\Service\HomeHandler; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\Routing\Annotation\Route; 
 
class MainController extends AbstractController 
{ 
 
    #[Route('/', name: 'home')] 
    public function index(HomeHandler $handler) 
    { 
        $context = $handler(); 
        return $this->render('homepage.html.twig',$context); 
    } 
 
} 
 
Приклад коду репозиторію FormController: 
<?php 
 
namespace App\Controller; 
 
use App\Entity\FormMessages; 
use App\Service\HomeFormHandler; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\Routing\Annotation\Route; 
 
class FormController extends AbstractController 
{ 
    #[Route('/home_form', name: 'home_form')] 
    public function index(Request $request, HomeFormHandler $handler): Response 
    { 
        $user = $this->getUser(); 
        $userFlag = false; 
        if ($user) { 
            $userFlag = true; 
        } 
        return $handler($user, $request, $userFlag); 
    } 
} 
 
Приклад коду репозиторію UserCabinetController: 
<?php 
 
namespace App\Controller; 
 
use App\Entity\AppointmentStatus; 
use App\Model\ServicesType; 
use App\Repository\AppointmentRepository; 
use App\Repository\AppointmentStatusRepository; 
use App\Repository\CoursesStudentRepository; 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;  
 
 
use Symfony\Component\HttpFoundation\File\Exception\FileException; 
use Symfony\Component\HttpFoundation\File\UploadedFile; 
use Symfony\Component\HttpKernel\KernelInterface; 
use Symfony\Component\Routing\Annotation\Route; 
 
class UserCabinetController extends AbstractController 
{ 
    private UserRepository $userRepository; 
    private AppointmentRepository $appointmentRepository; 
    private ServicesRepository $servicesRepository; 
    private $kernel; 
    private $em; 
    private CoursesStudentRepository $coursesStudentRepository; 
    private AppointmentStatusRepository $appointmentStatusRepository; 
 
    public function __construct( 
        UserRepository              $userRepository, 
        AppointmentRepository       $appointmentRepository, 
        KernelInterface             $kernel, 
        EntityManagerInterface      $em, 
        CoursesStudentRepository    $coursesStudentRepository, 
        ServicesRepository          $servicesRepository, 
        AppointmentStatusRepository $appointmentStatusRepository 
    ) 
    { 
        $this->userRepository = $userRepository; 
        $this->appointmentRepository = $appointmentRepository; 
        $this->kernel = $kernel; 
        $this->em = $em; 
        $this->coursesStudentRepository = $coursesStudentRepository; 
        $this->servicesRepository = $servicesRepository; 
        $this->appointmentStatusRepository = $appointmentStatusRepository; 
    } 
 
    #[Route('/master_cabinet', name: 'master_cabinet')] 
    public function masterCabinet() 
    { 
        $courses = []; 
        $services = []; 
        $master = $this->getUser(); 
        $statuses = []; 
        $userAppointments = $this->appointmentRepository->findBy(['employer' => $master]); 
        $coursesStudent = $this->coursesStudentRepository->findBy(['user' => $master]); 
        foreach ($coursesStudent as $courseStudent) { 
            $courses[] = $courseStudent->getKourse(); 
        } 
        $masterDetails = $master->getUserDetails(); 
        $masterAbilities = $masterDetails->getAbilitys(); 
        foreach ($masterAbilities as $masterAbility) { 
            $services = $this->servicesRepository->findBy(['category' => $masterAbility, 'type' => 
ServicesType::SERVICE]); 
        } 
        $statuses = $this->appointmentStatusRepository->findAll(); 
        return $this->render('master_cabinet.html.twig', [ 
            'user' => $master, 
            'userAppointments' => $userAppointments, 
            'courses' => $courses, 
            'services' => $services, 
            'statuses' => $statuses, 
        ]); 
    } 
 
    #[Route('/user_cabinet', name: 'user_cabinet')] 
    public function userСabinet() 
    { 
        $courses = []; 
        $user = $this->getUser(); 
        $userAppointments = $this->appointmentRepository->findBy(['user' => $this->getUser()]); 
        $coursesStudent = $this->coursesStudentRepository->findBy(['user' => $this->getUser()]); 
        foreach ($coursesStudent as $courseStudent) { 
            $courses[] = $courseStudent->getKourse(); 
        } 
        return $this->render('user_cabinet.html.twig', [ 
            'user' => $user, 
            'userAppointments' => $userAppointments, 
            'courses' => $courses, 
        ]); 
    } 
 
 
 
    #[Route('/user_save_logo', name: 'user_save_logo')] 
    public function userSaveLogo(Request $request, EntityManagerInterface $em): JsonResponse 
    { 
        $file = $request->files->get('file'); 
        $publicDirectory = $this->getParameter('kernel.project_dir') . '/public/images/user-photo/'; 
        $user = $this->getUser(); 
 
        if (!$file instanceof UploadedFile) { 
            return $this->json(['status' => 'error', 'message' => 'No file uploaded.'], 
Response::HTTP_BAD_REQUEST); 
        } 
 
        $fileName = md5(uniqid()) . '.' . $file->guessExtension(); 
 
        try { 
            $file->move($publicDirectory, $fileName); 
            $user->setLogo($fileName); 
            $em->persist($user); 
            $em->flush(); 
 
            return $this->json(['status' => 'success', 'fileName' => $fileName]); 
        } catch (FileException $e) { 
            return $this->json(['status' => 'error', 'message' => 'File upload failed.'], 
Response::HTTP_INTERNAL_SERVER_ERROR); 
        } 
    } 
} 
 
Приклад коду репозиторію TelegramBotController: 
<?php 
 
namespace App\Controller; 
 
use App\Entity\Appointment; 
use App\Entity\User; 
use GuzzleHttp\Client; 
use GuzzleHttp\Exception\RequestException; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\Routing\Annotation\Route; 
 
class TelegramBotController extends AbstractController 
{ 
//https://api.telegram.org/bot7032467161:AAGVsDOn5UQ4i-jS8ZI5LMXZWHDizsP6htE/getUpdates 
    private Client $client; 
 
    public function __construct(Client $client) 
    { 
        $this->client = $client; 
    } 
 
    #[Route("/get-telegram-updates", name: "get_telegram_updates")] 
    public function getUpdates(Request $request): Response 
    { 
        $token = $this->getParameter('telegram_http_api_token'); 
        $uri = 'https://api.telegram.org/bot' . $token; 
        $uriGetUpdates = $uri . '/getUpdates'; 
        $uriSendMessage = $uri . '/sendMessage?'; 
        try { 
            $response = $this->client->request('GET', $uriGetUpdates); 
            $data = json_decode($response->getBody()->getContents(), true); 
            $last = array_key_last($data['result']); 
            $chatId = $data['result'][$last]['message']['chat']['id']; 
            $userId = $data['result'][$last]['message']['from']['id']; 
            $getQuery = array( 
                "chat_id" => $chatId, 
                "text" => "<b>User ID: </b>" . $userId, 
                "parse_mode" => "html",); 
            $sendMessage = $this->client->request('GET', $uriSendMessage . 
http_build_query($getQuery)); 
            return new Response(json_encode($sendMessage)); 
        } catch (\Exception $e) { 
            return new Response("Error: " . $e->getMessage()); 
        } 
 
 
    } 
 
    public function sendAppointmentMessage(Appointment $appointment, User $user, User $master): 
Response 
    { 
        $token = $this->getParameter('telegram_http_api_token'); 
        $uri = 'https://api.telegram.org/bot' . $token; 
        $uriSendMessage = $uri . '/sendMessage?'; 
        try { 
            $getQuery = array( 
                "chat_id" => $user->getUserDetails()->getTelegram(), 
                "text" => "<b>Привіт, ви записалися на процедуру!</b> \n <b> Процедура: </b>" . 
$appointment->getService()->getName() . " \n <b>Час:</b> " . $appointment->getTime()->format('Y-m-d 
H:i:s') . " \n", 
                "parse_mode" => "html",); 
            $sendMessage = $this->client->request('GET', $uriSendMessage . 
http_build_query($getQuery)); 
 
            $getQueryMaster = array( 
                "chat_id" => $master->getUserDetails()->getTelegram(), 
                "text" => "<b>Привіт, до вас записалися на процедуру!</b> \n <b> Процедура: </b>" . 
$appointment->getService()->getName() . 
                    " \n <b>Час:</b> " . $appointment->getTime()->format('Y-m-d H:i:s') . " \n <b> 
Клієнт:</b> " . 
                    $master->getFirstname() . " " . $master->getLastname() . " \n <b>Телефон 
клієнта:</b> " . $user->getPhone(), 
                "parse_mode" => "html",); 
            $sendMessage = $this->client->request('GET', $uriSendMessage . 
http_build_query($getQueryMaster)); 
            return new Response(json_encode($sendMessage)); 
        } catch (\Exception $e) { 
            return new Response("Error: " . $e->getMessage()); 
        } 
    } 
 
    public function sendRegisterMessage(User $user, $chatId) 
    { 
        $token = $this->getParameter('telegram_http_api_token'); 
        $uri = 'https://api.telegram.org/bot' . $token; 
        $uriSendMessage = $uri . '/sendMessage?'; 
        try { 
            $getQuery = array( 
                "chat_id" => $chatId, 
                "text" => "<b>Привіт," . $user->getFirstname() . " ви  успішно зареєстровані!</b> 
\n", 
                "parse_mode" => "html",); 
            $sendMessage = $this->client->request('GET', $uriSendMessage . 
http_build_query($getQuery)); 
 
        } catch (\Exception $e) { 
        } 
    } 
 
    #[Route("/waiting_telegram_chat_id", name: "waiting_telegram_chat_id")] 
    public function waitingUserChatId(Request $request) 
    { 
        $client = new Client(); 
        $token = $this->getParameter('telegram_http_api_token'); 
        $uri = 'https://api.telegram.org/bot' . $token; 
        $uriGetUpdates = $uri . '/getUpdates'; 
        $interval = 1; 
        $duration = 360; 
        $userName = $request->get('userName'); 
        $endTime = time() + $duration; 
 
        while (time() < $endTime) { 
            try { 
                $response = $this->client->request('GET', $uriGetUpdates); 
                $data = json_decode($response->getBody()->getContents(), true); 
                $last = array_key_last($data['result']); 
                $userNameFromChat = $data['result'][$last]['message']['from']['username']; 
                if ($userNameFromChat == $userName) { 
                    $uriSendMessage = $uri . '/sendMessage?'; 
                    try { 
                        $getQuery = array( 
                            "chat_id" => $data['result'][$last]['message']['chat']['id'], 
                            "text" => "<b>Привіт," . $userNameFromChat . " ваш чат айди успішно 
встановлено тепер ми можено вам надсилати повідомлення!</b> \n", 
                            "parse_mode" => "html",); 
 
 
                        $this->client->request('GET', $uriSendMessage . 
http_build_query($getQuery)); 
 
                    } catch (\Exception $e) { 
                    } 
                    return new Response($data['result'][$last]['message']['chat']['id']); 
                } 
            } catch (RequestException $e) { 
                return new Response("HTTP Request failed: " . $e->getMessage() . "\n"); 
            } 
 
            sleep($interval); 
        } 
    } 
 
} 
 
Приклад коду репозиторію CoursesController: 
<?php 
 
namespace App\Controller; 
 
use App\Service\Courses\CoursesAppointmentHandler; 
use App\Service\Courses\CoursesAppointmentSaveHandler; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Component\HttpFoundation\Response; 
 
class CoursesController extends AbstractController 
{ 
    #[Route('/courses', name: 'courses')] 
    public function courses(): Response 
    { 
        return $this->render('courses.html.twig', [ 
        ]); 
    } 
 
    #[Route('/courses_appointment', name: 'courses_appointment')] 
    public function coursesAppointment(Request $request, CoursesAppointmentHandler $handler): 
Response 
    { 
        $context = $handler($request); 
        return $this->render('courses_appointment.html.twig', $context); 
    } 
 
    #[Route('/courses_appointment_save', name: 'courses_appointment_save')] 
    public function coursesAppointmentSave(Request $request, CoursesAppointmentSaveHandler 
$handler): JsonResponse 
    { 
        $user = $this->getUser(); 
        return $handler($request, $user); 
    } 
} 
 
Приклад коду репозиторію AppointmentController: 
<?php 
 
namespace App\Controller; 
 
use App\Service\Appointment\AppointmentHandler; 
use App\Service\Appointment\AppointmentSaveHandler; 
use App\Service\Appointment\AppointmentUpdateHandler; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\Routing\Annotation\Route; 
 
class AppointmentController extends AbstractController 
{ 
 
 
    #[Route('/appointment', name: 'appointment')] 
    public function index(Request $request, AppointmentHandler $appointmentHandler): Response 
    { 
        $context = $appointmentHandler($request); 
        return $this->render('appointment.html.twig', $context); 
    } 
 
    #[Route('/appointment_update', name: 'appointment_update')] 
    public function update(Request $request, AppointmentUpdateHandler $appointmentUpdateHandler): 
Response 
    { 
        $appointmentUpdateHandler($request); 
        $referer = $request->headers->get('referer'); 
        return $this->redirect($referer); 
    } 
 
    #[Route('/appointment_save', name: 'appointment_save')] 
    public function save(Request $request, AppointmentSaveHandler $appointmentSaveHandler): 
JsonResponse 
    { 
        $user = $this->getUser(); 
        return $appointmentSaveHandler($request, $user); 
    } 
} 
 
Приклад коду репозиторію AboutAsController: 
<?php 
 
namespace App\Controller; 
 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Component\HttpFoundation\Response; 
 
class AboutAsController extends AbstractController 
{ 
    #[Route('/about_us', name: 'about_us')] 
    public function aboutUs(): Response 
    { 
        return $this->render('about_us.html.twig', []); 
    } 
} 
 
Приклад коду репозиторію RegistrationController: 
<?php 
 
namespace App\Controller\Security; 
 
use App\Service\RegistrationHandler; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\Routing\Annotation\Route; 
 
class RegistrationController extends AbstractController 
{ 
    #[Route('/register_page', name: 'register_page')] 
    public function index(): Response 
    { 
        return $this->render('register.html.twig', [ 
        ]); 
    } 
 
    #[Route('/register', name: 'register')] 
    public function register(Request $request, RegistrationHandler $handler): JsonResponse 
    { 
        return $handler($request); 
 
    } 
} 
 
Приклад коду репозиторію NewPassController: 
<?php 
 
namespace App\Controller\Security; 
 
use App\Service\ChangePasswordHandler; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\Routing\Annotation\Route; 
 
class NewPassController extends AbstractController 
{ 
    #[Route('/new_user_pass', name: 'new_user_pass')] 
    public function index(Request $request, ChangePasswordHandler $changePasswordHandler): 
JsonResponse 
    { 
        $user = $this->getUser(); 
        return $changePasswordHandler($request, $user); 
 
    } 
 
} 
 
Приклад коду репозиторію NewPassController: 
<?php 
 
namespace App\Controller\Security; 
 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Bundle\SecurityBundle\Security; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\Routing\Annotation\Route; 
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils; 
 
class LoginLogoutController extends AbstractController 
{ 
    #[Route('/login', name: 'app_login')] 
    public function index(AuthenticationUtils $authenticationUtils): Response 
    { 
        // get the login error if there is one 
        $error = $authenticationUtils->getLastAuthenticationError(); 
        $lastUsername = $authenticationUtils->getLastUsername(); 
        return $this->render('login/index.html.twig', [ 
            'controller_name' => 'LoginLogoutController', 
            'last_username' => $lastUsername, 
            'error' => $error, 
        ]); 
    } 
 
    #[Route('/logout', name: 'app_logout')] 
    public function logout(Security $security) 
    { 
        $response = $security->logout(); 
        $response = $security->logout(false); 
    } 
 
} 
 
Приклад коду репозиторію UserAdmin: 
<?php 
 
namespace App\Admin; 
 
use Sonata\AdminBundle\Admin\AbstractAdmin; 
use Sonata\AdminBundle\Datagrid\DatagridMapper; 
use Sonata\AdminBundle\Datagrid\ListMapper; 
use Sonata\AdminBundle\Form\FormMapper; 
use Sonata\AdminBundle\Show\ShowMapper; 
use Symfony\Component\Form\Extension\Core\Type\TextType; 
 
 
 
final class UserAdmin extends AbstractAdmin 
{ 
    protected function configureFormFields(FormMapper $form): void 
    { 
        $form->add('firstName', TextType::class) 
            ->add('lastName', TextType::class) 
            ->add('email', TextType::class) 
            ->add('phone', TextType::class) 
            ->add('logo', TextType::class); 
    } 
 
    protected function configureDatagridFilters(DatagridMapper $datagrid): void 
    { 
        $datagrid 
            ->add('id') 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('phone') 
            ->add('logo'); 
    } 
    protected function configureListFields(ListMapper $list): void 
    { 
        $list->addIdentifier('id', null, ['editable' => false]) 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('roles') 
            ->add('phone') 
            ->add('logo'); 
    } 
    protected function configureShowFields(ShowMapper $show): void 
    { 
        $show->add('id') 
            ->add('firstName') 
            ->add('lastName') 
            ->add('email') 
            ->add('roles') 
            ->add('phone') 
            ->add('logo'); 
    } 
} 
 
 
МОДЕЛІ  
 
Приклад коду моделі CourseStatus: 
<?php 
 
namespace App\Model; 
 
enum CourseStatus: string 
{ 
    case ACTIVE = 'ACTIVE'; 
    case INACTIVE = 'INACTIVE'; 
    case  COMPLETED = 'COMPLETED'; 
    case  CANCELED = 'CANCELED'; 
    case ARCHIVED = 'ARCHIVED'; 
    case ENROLLMENT = 'ENROLLMENT'; 
} 
 
Приклад коду моделі CourseStudent: 
<?php 
 
namespace App\Model; 
 
enum CourseStudent: string 
{ 
    case ACTIVE = 'ACTIVE'; 
    case  COMPLETED = 'COMPLETED'; 
    case  CANCELED = 'CANCELED'; 
    case ARCHIVED = 'ARCHIVED'; 
    case ENROLLMENT = 'ENROLLMENT'; 
} 
 
Приклад коду моделі ServicesType: 
<?php 
 
namespace App\Model; 
 
enum ServicesType: string 
{ 
    case CONSUMABLE_ITEM = 'CONSUMABLE_ITEM'; 
    case SERVICE = 'SERVICE'; 
 
} 
 
 
 СЕРВІСИ 
 
Приклад коду сервісу HomeHandler: 
<?php 
declare(strict_types=1); 
 
namespace App\Service; 
 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
 
class HomeHandler 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
 
    public function __construct(UserRepository $userRepository, ServicesRepository 
$servicesRepository) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
    } 
 
    public function __invoke(): array 
    { 
        $masters = $this->userRepository->findByRole('ROLE_MASTER'); 
        $services = $this->servicesRepository->findServicesByNumber(5); 
        return [ 
            'masters' => $masters, 
            'services' => $services, 
        ]; 
 
    } 
} 
 
Приклад коду сервісу HomeFormHandler: 
<?php 
 
namespace App\Service; 
 
use App\Entity\FormMessages; 
use App\Entity\User; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
 
class HomeFormHandler 
{ 
    private $entityManager; 
 
    public function __construct(EntityManagerInterface $entityManager) 
    { 
        $this->entityManager = $entityManager; 
    } 
 
    public function __invoke(?User $user, Request $request, bool $userFlag): Response 
    { 
        $formMessages = new FormMessages(); 
 
        if ($userFlag) { 
            $formMessages->setUser($user); 
            $formMessages->setEmail($user->getEmail()); 
            $formMessages->setFirstName($user->getFirstname()); 
            $formMessages->setLastName($user->getLastname()); 
        } else { 
            $formMessages->setUser(null); 
            $formMessages->setEmail($request->request->get('email')); 
            $formMessages->setFirstName($request->request->get('firstName')); 
            $formMessages->setLastName($request->request->get('lastName')); 
        } 
 
 
 
        $formMessages->setMessage($request->request->get('ask')); 
        $formMessages->setCreatedAt(new \DateTime()); 
        $this->entityManager->persist($formMessages); 
        $this->entityManager->flush(); 
 
        return new Response('', 200); 
    } 
} 
 
Приклад коду сервісу RegistrationHandler: 
<?php 
 
namespace App\Service; 
 
use App\Controller\TelegramBotController; 
use App\Entity\User; 
use App\Entity\UserDetails; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; 
 
class RegistrationHandler 
{ 
    private EntityManagerInterface $entityManager; 
    private UserPasswordHasherInterface $passwordHasher; 
    private TelegramBotController $telegramBotController; 
 
    public function __construct( 
        UserPasswordHasherInterface $passwordHasher, 
        EntityManagerInterface      $entityManager, 
        TelegramBotController $telegramBotController) 
    { 
        $this->entityManager = $entityManager; 
        $this->passwordHasher = $passwordHasher; 
        $this->telegramBotController = $telegramBotController; 
    } 
 
    public function __invoke($request): JsonResponse 
    { 
        $user = new User(); 
        $userDetails = new UserDetails(); 
        $user->setEmail($request->get('email')); 
        $user->setFirstname($request->get('firstname')); 
        $user->setLastname($request->get('lastname')); 
        $userDetails->setUser($user); 
        $userDetails->setTelegram($request->get('telegram') ?? null); 
        $userDetails->setTelegramChatId($request->get('telegramChatId') ?? ''); 
        $user->setUserDetails($userDetails); 
        $user->setPhone($request->get('phone')); 
        $user->setUserDetails(null); 
        $user->setRoles((array)'ROLE_USER'); 
        $user->setLogo('logo.png'); 
        $plaintextPassword = ($request->get('confirm_password')); 
        $hashedPassword = $this->passwordHasher->hashPassword( 
            $user, 
            $plaintextPassword 
        ); 
        $user->setPassword($hashedPassword); 
        $this->entityManager->persist($userDetails); 
        $this->entityManager->persist($user); 
        $this->entityManager->flush(); 
 
        if ($user->getId()) { 
            if ($userDetails->getTelegramChatId()) { 
                $this->telegramBotController->sendRegisterMessage($user, $userDetails-
>getTelegramChatId()); 
            } 
            return new JsonResponse(['success' => true, 'message' => 'Ви успішно зареєстровані!']); 
        } else { 
            return new JsonResponse(['success' => false, 'message' => 'Помилка, спробуйте будь ласка 
знову пізніше']); 
        } 
    } 
} 
 
 
Приклад коду сервісу ChangePasswordHandler: 
<?php 
 
namespace App\Service; 
 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; 
 
class ChangePasswordHandler 
{ 
    private EntityManagerInterface $entityManager; 
    private UserPasswordHasherInterface $passwordHasher; 
    public function __construct( 
        UserPasswordHasherInterface $passwordHasher, 
        EntityManagerInterface      $entityManager) 
    { 
        $this->entityManager = $entityManager; 
        $this->passwordHasher = $passwordHasher; 
    } 
    public function __invoke($request, $user): JsonResponse 
    { 
        $userOldPass = $request->request->get('oldPass'); 
        $userNewPass = $request->request->get('newPass'); 
 
        if ($this->passwordHasher->isPasswordValid($user, $userOldPass)) { 
            $newHashedPassword = $this->passwordHasher->hashPassword($user, $userNewPass); 
            $user->setPassword($newHashedPassword); 
            $this->entityManager->persist($user); 
            $this->entityManager->flush(); 
            return new JsonResponse(['message' => 'Password changed successfully'], 200); 
        } else { 
            return new JsonResponse(['message' => 'Old password entered incorrectly'], 400); 
        } 
    } 
} 
 
Приклад коду сервісу CoursesAppointmentHandler: 
<?php 
 
namespace App\Service\Courses; 
 
use App\Model\CourseStatus; 
use App\Repository\CoursesRepository; 
use App\Repository\CoursesStudentRepository; 
 
class CoursesAppointmentHandler 
{ 
    private CoursesRepository $courseRepository; 
    private CoursesStudentRepository $courseStudentRepository; 
 
    public function __construct( 
        CoursesRepository        $courseRepository, 
        CoursesStudentRepository $courseStatusRepository 
    ) 
    { 
        $this->courseRepository = $courseRepository; 
        $this->courseStudentRepository = $courseStatusRepository; 
 
    } 
 
    public function __invoke($request): array 
    { 
        $coursesInfo = []; 
        if ($request->query->has('type')) { 
            $courseType = $request->query->get('type'); 
            $courses = $this->courseRepository->findBy(['status' => CourseStatus::ENROLLMENT->value, 
'courseType' => $courseType]); 
        } else { 
            $courses = $this->courseRepository->findBy(['status' => CourseStatus::ENROLLMENT-
>value]); 
        } 
        foreach ($courses as $course) { 
            $coursesInfo[$course->getId()] = $course->getCoursesInfo(); 
 
 
        } 
        return [ 
            'courses' => $courses, 
            'coursesInfos' => $coursesInfo, 
        ]; 
    } 
} 
 
Приклад коду сервісу CoursesAppointmentSaveHandler: 
<?php 
 
namespace App\Service\Courses; 
 
use App\Entity\CoursesStudent; 
use App\Model\CourseStudent; 
use App\Repository\CoursesRepository; 
use App\Repository\CoursesStudentRepository; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
 
class CoursesAppointmentSaveHandler 
{ 
    private CoursesRepository $courseRepository; 
    private CoursesStudentRepository $courseStudentRepository; 
    private EntityManagerInterface $entityManager; 
 
    public function __construct( 
        CoursesRepository        $courseRepository, 
        CoursesStudentRepository $courseStatusRepository, 
        EntityManagerInterface   $entityManager 
    ) 
    { 
        $this->courseRepository = $courseRepository; 
        $this->courseStudentRepository = $courseStatusRepository; 
        $this->entityManager = $entityManager; 
    } 
 
    public function __invoke($request, $user) 
    { 
        $courseId = $request->get('course'); 
        $course = $this->courseRepository->findOneBy(['id' => $courseId]); 
        $courseStudent = $this->courseStudentRepository->findOneBy(['user' => $user->getId(), 
'kourse' => $course->getId()]); 
        if ($courseStudent) { 
            return new JsonResponse(['success' => 'isInBase', 'message' => 'Ви вже записані на цей 
курс!']); 
        } else { 
            $courseStudent = new CoursesStudent(); 
            $courseStudent->setKourse($course); 
            $courseStudent->setUser($user); 
            $courseStudent->setStartTime($course->getStartTime()); 
            $courseStudent->setEndTime($course->getEndTime()); 
            $courseStudent->setStatus(CourseStudent::ENROLLMENT->value); 
 
            $this->entityManager->persist($courseStudent); 
            $this->entityManager->flush(); 
            if ($courseStudent->getId()) { 
                return new JsonResponse(['success' => true, 'message' => 'Ви успішно записані!']); 
            } else { 
                return new JsonResponse(['success' => false, 'message' => 'Помилка, спробуйте будь 
ласка знову пізніше']); 
            } 
        } 
    } 
} 
 
 
Приклад коду сервісу AppointmentHandler: 
<?php 
 
namespace App\Service\Appointment; 
 
use App\Model\ServicesType; 
use App\Repository\AppointmentRepository; 
use App\Repository\AppointmentStatusRepository; 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
 
class AppointmentHandler 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
    private AppointmentRepository $appointmentRepository; 
    private AppointmentStatusRepository $appointmentStatus; 
 
    public function __construct( 
        UserRepository              $userRepository, 
        ServicesRepository          $servicesRepository, 
        AppointmentRepository       $appointmentRepository, 
        AppointmentStatusRepository $appointmentStatus 
    ) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
        $this->appointmentRepository = $appointmentRepository; 
        $this->appointmentStatus = $appointmentStatus; 
    } 
 
    public function __invoke($request): array 
    { 
        $services = []; 
        $selectedMaster = null; 
        $masters = $this->userRepository->findByRole('ROLE_MASTER'); 
        $consumableItems = []; 
        $masterAbilities = []; 
        if ($request->query->has('master')) { 
            $selectedMaster = $this->userRepository->findOneBy(['id' => $request->query-
>get('master')]); 
            $masterDetails = $selectedMaster->getUserDetails(); 
            $masterAbilities = $masterDetails->getAbilitys(); 
            foreach ($masterAbilities as $masterAbility) { 
                $services = $this->servicesRepository->findBy(['category' => $masterAbility, 'type' 
=> ServicesType::SERVICE]); 
            } 
        } else { 
            $services = $this->servicesRepository->findBy(['type' => ServicesType::SERVICE]); 
        } 
        foreach ($services as $service) { 
            $temp = $this->servicesRepository->findBy(['category' => $service->getCategory(), 'type' 
=> ServicesType::CONSUMABLE_ITEM]); 
            if ($temp) { 
                $consumableItems[$service->getCategory()] = $temp; 
            } 
        } 
        return [ 
            'masters' => $masters, 
            'selectedMaster' => $request->query->get('master'), 
            'masterAbilities' => $services, 
            'consumableItems' => $consumableItems, 
        ]; 
    } 
} 
 
 
Приклад коду сервісу AppointmentSaveHandler: 
<?php 
 
namespace App\Service\Appointment; 
 
use App\Controller\TelegramBotController; 
use App\Entity\Appointment; 
use App\Repository\AppointmentRepository; 
use App\Repository\AppointmentStatusRepository; 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
use Doctrine\ORM\EntityManagerInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use DateTime; 
 
class AppointmentSaveHandler 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
    private AppointmentRepository $appointmentRepository; 
    private AppointmentStatusRepository $appointmentStatus; 
    private EntityManagerInterface $entityManager; 
    private TelegramBotController $telegramBotController; 
 
    public function __construct( 
        UserRepository              $userRepository, 
        ServicesRepository          $servicesRepository, 
        AppointmentRepository       $appointmentRepository, 
        AppointmentStatusRepository $appointmentStatus, 
        EntityManagerInterface      $entityManager, 
        TelegramBotController       $telegramBotController, 
    ) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
        $this->appointmentRepository = $appointmentRepository; 
        $this->appointmentStatus = $appointmentStatus; 
        $this->entityManager = $entityManager; 
        $this->telegramBotController = $telegramBotController; 
    } 
 
    public function __invoke($request, $user): JsonResponse 
    { 
        $master = $this->userRepository->findOneBy(['id' => $request->get('master')]); 
        $service = $this->servicesRepository->findOneBy(['id' => $request->get('service')]); 
        $status = $this->appointmentStatus->findOneBy(['name' => "WAITING"]); 
        $appointment = new Appointment(); 
        $appointment->setEmployer($master); 
        $appointment->setCreateAt(new DateTime()); 
        $appointment->setUser($user); 
        $appointment->setService($service); 
        $appointment->setPrice($request->get('price')); 
        $appointment->setTime(new DateTime($request->get('time'))); 
        $appointment->setStatus($status); 
 
        $this->entityManager->persist($appointment); 
        $this->entityManager->flush(); 
 
        $this->telegramBotController->sendAppointmentMessage($appointment, $user, $master); 
        if ($appointment->getId()) { 
            return new JsonResponse(['success' => true, 'message' => 'Ви успішно записані!']); 
        } else { 
            return new JsonResponse(['success' => false, 'message' => 'Помилка, спробуйте будь ласка 
знову пізніше']); 
        } 
    } 
} 
 
 
Приклад коду сервісу AppointmentUpdateHandler: 
<?php 
 
namespace App\Service\Appointment; 
 
use App\Repository\AppointmentRepository; 
use App\Repository\AppointmentStatusRepository; 
use App\Repository\ServicesRepository; 
use App\Repository\UserRepository; 
use Doctrine\ORM\EntityManagerInterface; 
use DateTime; 
 
class AppointmentUpdateHandler 
{ 
    private UserRepository $userRepository; 
    private ServicesRepository $servicesRepository; 
    private AppointmentRepository $appointmentRepository; 
    private AppointmentStatusRepository $appointmentStatus; 
    private EntityManagerInterface $entityManager; 
 
    public function __construct( 
        UserRepository              $userRepository, 
        ServicesRepository          $servicesRepository, 
        AppointmentRepository       $appointmentRepository, 
        AppointmentStatusRepository $appointmentStatus, 
        EntityManagerInterface      $entityManager 
    ) 
    { 
        $this->userRepository = $userRepository; 
        $this->servicesRepository = $servicesRepository; 
        $this->appointmentRepository = $appointmentRepository; 
        $this->appointmentStatus = $appointmentStatus; 
        $this->entityManager = $entityManager; 
    } 
 
    public function __invoke($request) 
    { 
        $appointment = $this->appointmentRepository->findOneBy(['id' => $request->get('id')]); 
        $service = $this->servicesRepository->findOneBy(['id' => $request->get('service')]); 
        $status = $this->appointmentStatus->findOneBy(['id' => $request->get('status')]); 
        if ($appointment) { 
            $appointment->setPrice($request->get('appointmentPrice')); 
            $appointment->setTime(new DateTime($request->get('appointmentTime'))); 
            $appointment->setStatus($status); 
            $appointment->setService($service); 
            $this->entityManager->persist($appointment); 
            $this->entityManager->flush(); 
        } 
    } 
} 
 
 
JS СКРИПТІВ 
 
Приклад коду js скрипта appointment.js: 
checkJQuery(); 
 
function checkJQuery() { 
    $('input[name="time"]').daterangepicker({ 
        singleDatePicker: true, 
        showDropdowns: true, 
        timePicker: true, // Включает выбор времени 
        timePicker24Hour: true, // Используйте 24-часовой формат времени 
        timePickerIncrement: 5, // Интервал в минутах для выбора времени 
        minYear: 2024, 
        maxYear: parseInt(moment().format('YYYY'), 10), 
        startDate: moment().startOf('hour'), 
        endDate: moment().startOf('hour').add(32, 'hour'), 
        locale: { 
            format: 'DD.MM.YYYY HH:mm', 
            applyLabel: "Прийняти", 
            cancelLabel: "Відміна", 
            fromLabel: "Від", 
            toLabel: "До", 
            customRangeLabel: "Вибрати", 
            daysOfWeek: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"], 
            monthNames: [ 
                "Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", 
                "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень" 
            ], 
            firstDay: 1 
        } 
    }); 
    document.getElementById("service").addEventListener("change", function () { 
        displayNoneCostBlock(); 
        let totalSuma = 0; 
        let selectedServiceId = this.value; 
        let selectedServiceClass = this.options[this.selectedIndex].className; 
        let cost = this.options[this.selectedIndex].getAttribute('cost'); 
        let costBlock = document.getElementById("const_container_" + selectedServiceClass); 
        let consumable_items_costs = document.querySelectorAll("#const_container_" + 
selectedServiceClass + " .cost"); 
        consumable_items_costs.forEach(function (element) { 
            let textContent = element.textContent.trim().split(" "); 
            let price = textContent[0]; 
            totalSuma = totalSuma + Number(price); 
        }); 
        totalSuma = totalSuma + Number(cost); 
        let priceBlock = document.querySelector("#const_container_" + selectedServiceClass + ' 
.service_price'); 
 
        priceBlock.innerHTML = "<div class=\"row\">\n" + 
            " <div class=\"col text-start\">Послуга</div>\n" + 
            " <div class=\"col text-end cost\"> " + cost + " грн</div>\n" + 
            " </div>"; 
        let costTotalSumaElement = document.querySelector("#const_container_" + selectedServiceClass 
+ ' .total_suma' + ' input'); 
        console.log("#const_container_" + selectedServiceClass + ' .total_suma' + ' input'); 
        console.log(costTotalSumaElement); 
        costTotalSumaElement.value = totalSuma+" грн"; 
        costBlock.style.display = "block"; 
    }); 
 
    function displayNoneCostBlock() { 
        let costBlocks = document.querySelectorAll(".const_container"); 
        if (costBlocks) { 
            costBlocks.forEach(function (element) { 
                element.style.display = "none"; 
            }); 
        } 
    } 
 
    $('#appointment_form').submit(function (event) { 
        event.preventDefault(); 
        var formData = $(this).serialize(); 
        console.log(formData);  
 
 
        $.ajax({ 
            type: 'POST', 
            url: $(this).attr('action'), 
            data: formData, 
            success: function (response) { 
                $('.alert-success').css('display', 'block'); 
                $('.alert-success').text('Ви успішно зареєстровані!'); 
                setTimeout(function () { 
                    window.history.back(); 
                }, 1000); 
            }, 
            error: function (xhr, status, error) { 
                $('.alert-danger').css('display', 'block'); 
                $('.alert-danger').text('Помилка, спробуйте будь ласка знову пізніше!'); 
            } 
        }); 
    }); 
 
} 
  
Приклад коду js скрипта course_appointment.js: 
 
let courseBlock; 
document.getElementById("course").addEventListener("change", function () { 
    displayNoneCourseBlock(); 
    var selectedCourseId = this.value; 
    if(courseBlock){ 
        courseBlock.style.display = "none"; 
    } 
    courseBlock = document.getElementById("course_block_" + selectedCourseId); 
    courseBlock.style.display = "block"; 
}); 
function displayNoneCourseBlock(){ 
    var courseBlocks = document.querySelector("course_block"); 
    if(courseBlocks){ 
        courseBlocks.forEach(function(element) { 
            element.style.display = "none"; 
        }); 
    } 
} 
$('#appointment_form').submit(function (event) { 
    event.preventDefault(); 
    var formData = $(this).serialize(); 
    console.log(formData); 
    $.ajax({ 
        type: 'POST', 
        url: $(this).attr('action'), 
        data: formData, 
        success: function (response) { 
            if(response.success ==='isInBase'){ 
                $('.alert-danger').css('display', 'block'); 
                $('.alert-danger').text('Ви вже записані на цей курс!'); 
            }else { 
                $('.alert-success').css('display', 'block'); 
                $('.alert-success').text('Ви успішно зареєстровані!'); 
                setTimeout(function () { 
                    window.history.back(); 
                }, 1000); 
            } 
        }, 
        error: function (xhr, status, error) { 
            $('.alert-danger').css('display', 'block'); 
            $('.alert-danger').text('Помилка, спробуйте будь ласка 
знову пізніше!'); 
        } 
    }); 
}) 
; 
 
 
Приклад коду js скрипта header.js: 
const element = document.querySelector('#header'); 
const menuItems = document.querySelectorAll('#main_menu .nav-link'); 
function getCurrentColor(element) { 
    return window.getComputedStyle(element).getPropertyValue('color'); 
} 
 
const initialColor = getCurrentColor(menuItems[0]); 
 
window.addEventListener('scroll', () => { 
    const scrollPosition = window.scrollY; 
 
    if (scrollPosition > 10) { 
        element.style.position = 'fixed' 
        element.style.backgroundColor = '#fff'; 
        element.style.height = '110px'; 
        element.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)'; 
 
        menuItems.forEach(item => { 
            item.style.color = '#B69256'; 
        }); 
    } else { 
        element.style.position = 'relative' 
        element.style.boxShadow = 'none'; 
        element.style.backgroundColor = 'rgba(255,255,255,0)'; 
 
        menuItems.forEach(item => { 
            item.style.color = initialColor; 
        }); 
    } 
}); 
 
Приклад коду js скрипта home.js: 
$(document).ready(function () { 
    $('.contact.form').on('submit', function (e) { 
        e.preventDefault(); 
 
        var formData = $(this).serialize(); 
 
        $.ajax({ 
            url: $(this).attr('action'), 
            type: 'POST', 
            data: formData, 
            success: function (response) { 
                $('.alert-success').css('display', 'block'); 
                $('.alert-success').text('Ви успішно надіслали листа!'); 
                setTimeout(function () { 
                    location.reload(); 
                }, 1000); 
            }, 
            error: function (xhr, status, error) { 
                $('.alert-danger').css('display', 'block'); 
                $('.alert-danger').text('Помилка, спробуйте будь ласка знову пізніше!'); 
            } 
        }); 
    }); 
}); 
 
Приклад коду js скрипта post_registration.js: 
$(document).ready(function () { 
    const formInputs = document.querySelectorAll('.form-control.register_input'); 
    const registerButton = document.querySelector('.register_btn'); 
    const passwordInput = document.getElementById('password'); 
    const confirmPasswordInput = document.getElementById('confirm_password'); 
    const telegramButtonModal = document.getElementById('telegram_btn_modal'); 
    function validateInput(input, pattern, isOptional = false) { 
        const value = input.value.trim(); 
        const feedbackElement = input.nextElementSibling; 
        const isValid = value === '' ? isOptional : pattern.test(value); 
 
        if (isValid) { 
            input.classList.remove('is-invalid');  
 
 
            feedbackElement.style.display = 'none'; 
            if (input.id === 'telegram') { // Если это поле телеграм и оно валидно 
                telegramButtonModal.style.display = 'block'; // Показываем кнопку 
            } 
        } else { 
            input.classList.add('is-invalid'); 
            feedbackElement.style.display = 'block'; 
            if (input.id === 'telegram') { // Если это поле телеграм и оно невалидно 
                telegramButtonModal.style.display = 'none'; // Скрываем кнопку 
            } 
        } 
        checkFormValidity(); 
    } 
    function validatePasswordMatch() { 
        const feedbackElement = confirmPasswordInput.nextElementSibling; 
        if (passwordInput.value === confirmPasswordInput.value) { 
            confirmPasswordInput.classList.remove('is-invalid'); 
            feedbackElement.style.display = 'none'; 
        } else { 
            confirmPasswordInput.classList.add('is-invalid'); 
            feedbackElement.style.display = 'block'; 
            feedbackElement.textContent = 'Паролі не співпадають.'; 
        } 
        checkFormValidity();} 
    function checkFormValidity() { 
        const isAnyInvalid = Array.from(formInputs).some(input => input.classList.contains('is-
invalid')); 
        const isPasswordMismatch = passwordInput.value !== confirmPasswordInput.value; 
        registerButton.disabled = isAnyInvalid || isPasswordMismatch; 
    } 
    formInputs.forEach(input => { 
        input.addEventListener('input', function () { 
            validateInput(input, getPatternForInput(input), input.id === 'telegram'); 
            if (input === passwordInput || input === confirmPasswordInput) { 
                validatePasswordMatch(); 
            }});}); 
    function getPatternForInput(input) { 
        switch (input.id) { 
            case 'email': 
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 
            case 'firstname': 
            case 'lastname': 
                return /^[a-zA-Zа-яА-ЯіїІЇ]+$/; 
            case 'phone': 
                return /^\+?\d{10,15}$/; 
            case 'telegram': 
                return /^https?:\/\/t\.me\/[a-zA-Z0-9_]+$/; 
            case 'password': 
            case 'confirm_password': 
                return /.+/; 
            default: 
                return /.*/;}} 
    $('#telegram_btn_modal').click( function (event) { 
        let telegramUrl = $('#telegram').val(); 
        let username = telegramUrl.match(/[^\/]+$/)[0]; 
        $.ajax({ 
            type: 'GET', 
            url: 'http://diplom/waiting_telegram_chat_id', 
            data: {'userName': username}, 
            success: function (response) { 
                $('#telegramChatId').val(response); 
                $('#chatIdUserFromBot').text("Ваш chat id: "+response); 
            }, 
            error: function (xhr, status, error) { 
            }});}); 
    $('form_submit_btn').click(function (event) { 
        event.preventDefault(); 
        if (registerButton.disabled) { 
            alert('Будь ласка, заповніть всі обов\'язкові поля правильно і переконайтеся, що паролі 
збігаються перед відправкою.'); 
            return;} 
        var formData = $(this).serialize(); 
        $.ajax({ 
            type: 'POST', 
            url: $(this).attr('action'), 
            data: formData, 
            success: function (response) { 
                $('.alert-success').css('display', 'block'); 
                $('.alert-success').text('Ви успішно зареєстровані!');  
 
 
                window.history.back(); 
            }, 
            error: function (xhr, status, error) { 
                $('.alert-danger').css('display', 'block'); 
                $('.alert-danger').text('Помилка, спробуйте будь ласка знову пізніше!');}});});}); 
 
Приклад коду js скрипта swiper.js: 
var swiper = new Swiper(".mySwiper", { 
    centeredSlides: true, 
    slidesPerView: 3, 
    spaceBetween: 25, 
    autoplay: { 
        delay: 2500, 
        disableOnInteraction: false, 
    }, 
 
    pagination: { 
        el: ".swiper-pagination", 
        clickable: true, 
    }, 
    navigation: { 
        nextEl: ".swiper-button-next", 
        prevEl: ".swiper-button-prev", 
    }, 
}); 
                $('.alert-danger').text('Помилка, спробуйте будь ласка знову пізніше!');}});});}); 
 
Приклад коду js скрипта user_cabinet.js: 
document.getElementById('save-logo').addEventListener('click', function () { 
 
    var fileInput = document.getElementById('formFile'); 
    var file = fileInput.files[0]; 
 
    var formData = new FormData(); 
    formData.append('file', file); 
 
    fetch('/user_save_logo', { 
        method: 'POST', 
        body: formData 
    }) 
        .then(data => { 
            if (data.status === 'error') { 
 
            } else { 
                document.getElementById('alert-success').style.display = 'block'; 
                setTimeout(function () { 
                    document.getElementById('changeLogoModal').classList.remove('show'); 
                    document.getElementById('changeLogoModal').style.display = 'none'; 
                    document.querySelector('.modal-backdrop').classList.remove('show'); 
                    document.querySelector('.modal-backdrop').style.display = 'none'; 
                }, 500); 
                location.reload(); 
            } 
        }) 
        .catch(error => { 
            console.error('There was a problem with your fetch operation:', error); 
        }); 
}); 
 
 
document.getElementById('save-pass').addEventListener('click', function () { 
    var oldPass = document.getElementById('oldPass').value; 
    var newPass = document.getElementById('newPass').value; 
    var formData = new FormData(); 
    formData.append('oldPass', oldPass); 
    formData.append('newPass', newPass); 
 
    fetch('/new_user_pass', { 
        method: 'POST', 
        body: formData 
    }) 
        .then(data => { 
            if (data.status === 400) { 
 
 
                document.getElementById('alert-pass-danger').style.display = 'block'; 
            } else if (data.status === 200) { 
                document.getElementById('alert-pass-danger').style.display = 'none'; 
                document.getElementById('alert-pass-success').style.display = 'block'; 
                setTimeout(function () { 
                    document.getElementById('changePassModal').classList.remove('show'); 
                    document.getElementById('changePassModal').style.display = 'none'; 
                    document.querySelector('.modal-backdrop').classList.remove('show'); 
                    document.querySelector('.modal-backdrop').style.display = 'none'; 
                }, 500); 
                location.reload(); 
            } 
        }) 
        .catch(error => { 
            console.error('There was a problem with your fetch operation:', error); 
        }); 
}); 
 
 
ДОДАТОК В 
 
 
                                                                    ЗАТВЕРДЖЕНО 
                                                                    Зав.кафедри ІТП 
____________Прокопенко Т.О. 
“_____” _____________2024р. 
 
 
 
 
  Сайт компанії з надання послуг краси та здоров'я  
 
Макети сайту 
482 ЧДТУ 242280 – 01 12  
Листів 7 
 
Н 
 
Розробник                            _____________                     Декен Є.Р. 
Керівник                              _____________                      Рудницький С.В. 
Н. контроль                          _____________                     Рудницький С.В. 
 
                                                                                                                            
 
 
 
 
Черкаси, 2024
 
МАКЕТИ САЙТУ 
 
Приклад макету головної сторінки: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Рисунок 1 Головна сторінка 
 
Приклад макету сторінки курсів: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Рисунок 2 Сторінка курсів 
 
 
Приклад макету сторінки запису на курс: 
 
Рисунок 3 Сторінка запису на курси 
 
 
 Приклад макету сторінки логіну: 
 
Рисунок 4 Сторінка логіну 
 
 
Приклад макету сторінки запису на процедуру: 
 
Рисунок 5 Сторінка запису на процедуру 
 
 
Приклад макету сторінка кабінету юзера: 
 
Рисунок 6 Сторінка кабінету юзера 
 
 
 
Приклад макету сторінка кабінету майстра: 
 
Рисунок 7 Сторінка кабінету майстра