Сегодня будем отправлять форму заявки на вступление в клуб с сайта на сервер и там обрабатывать. Разберемся, каким образом браузер может обмениваться данными с сервером.
Что было на прошлом занятии?
- Разобрались, что такое виртуализация и контейнеры
- Разобрались в общих чертах, как обрабатываются запросы браузера на сервере
- Разобрались и настроили веб-сервер NginX
- Создали контейнеры NginX и php в Docker
- Подняли у себя на компьютере локально Docker-сервис
- Познакомились с языком программирования php
Что будет на этом уроке?
На этом уроке мы:
- Соберем результаты прошлых уроков в один проект
- Разберемся, каким образом можно отправлять формы с сайта на сервер
- Разберемся, как обрабатывать на сервере данные, отправленные с браузера
Если в процессе изучения что-то не понятно - не стесняйтесь спрашивать нас и других учеников в нашей открытой группе в телеграм:
Для тех, кто только к нам присоединился, ссылка на все уроки:
https://zen.yandex.ru/srmt22_webclasses
И ссылка на самый первый урок:
https://dzen.ru/media/id/62d57767cc38491671976547/62d577ce6a8de461fac89852
Собираем проекты в один.
Создайте папку нового проекта. У меня это будет srmt22-lsn15.
За основу возьмем проект с Урока 14.
https://github.com/madmaker/srmt22-lsn14/archive/refs/tags/v1.zip
Качайте, распаковывайте и копируйте все файлы в папку нового проекта.
Теперь скачайте проект с урока 13, распакуйте и все содержимое сложить в папку www в новом проекте.
https://github.com/madmaker/srmt22-lsn13/archive/refs/tags/v1.zip
Теперь откройте папку нового проекта в IDE (phpstorm, vscode или другая).
У нас получилась вот такая структура папок:
Давайте удалим лишние файлы:
/www/README.md - это файл от проекта lsn13. Такой же уже есть в корне от lsn14
/www/.gitignore - это файл от проекта lsn13. Такой же уже есть в корне от lsn14
/www/jsconfig.json - этот файл сделал vscode автоматически. Он не нужен.
Папку scss из /www/scss переместите на уровень выше - в корень. Будет /scss
Файл /www/index.php удалите.
Файл /www/index.html переименуйте в /www/index.php
О да, в php-сценариях может быть обычный html. Php-интерпретатор его не будет обрабатывать и выведет как есть.
Получилось вот так:
Теперь время поднять контейнеры.
Я, когда писал эту статью, наткнулся на такой момент: у меня были запущены контейнеры с прошлого урока и новые при запуске выдавали ошибку. Собственно, нужно старые погасить, потому что они используют те же порты. Сейчас покажу, как это делать.
Как погасить контейнеры Docker
- Открываем Терминал
- Вводим команду docker container ls - эта команда выведет список запущенных контейнеров.
Докер выведет список запущенных контейнеров. Если список пуст, то просто пропустите этот шаг.
Container ID - это идентификатор контейнера. Нам нужно их погасить.
Копируем ID первого контейнера и выполняем следующую команду:
docker container stop XXXXXX
XXXXXX - ID вашего контейнера.
У меня было так: docker container stop 9cc9e3fe2f45
В ответ выведет номер остановленного контейнера:
Повторяем все, пока список контейнеров не станет пустым.
Кстати, последние команды в терминале можно найти, нажав на клавишу "вверх" на клавиатуре.
Дальше по алгоритму:
- docker container ls
- Копируем ID очередного контейнера
- docker container stop XXXXX
- Повторяем, пока список запущенных контейнеров не будет пустым.
Еще один нюанс: у нас в прошлом уроке был создан контейнер с именем nginx. В этом проекте мы будем создавать такой контейнер и, разумеется получим ошибку. Поэтому контейнер с именем nginx нужно удалить.
Выполните команду:
docker container rm nginx
Контейнер будет удален.
Теперь поднимаем контейнеры нашего проекта.
В терминале в IDE выполните следующую команду:
docker-compose up
Результат запуска будет вроде такого:
Проверьте результат в Chrome: откройте адрес или 127.0.0.1, или localhost
Отправляем веб-форму на сервер
Итак, когда мы нажимаем на кнопку "Вступить в клуб бесплатно", у нас открывается диалог с веб-формой, которую мы делали.
Когда пользователь нажмет в форме кнопку "Поехали", нужно эту форму отправить на сервер. Давайте разбираться, как это делать.
Отправка формы без JS штатным путем.
У нас в 232-й строке файла index.php находится html-код формы.
<form>
<label for="phone">Телефон для связи</label>
<input type="tel" name="phone" pattern="\+[0-9]{9,13}" placeholder="+79991112233" required>
<button type="submit">Поехали!</button>
</form>
Для тега form существует атрибут action, который задает адрес, на который будет отправлена форма.
Изменим код таким образом:
<form action="enterToClubFormHandler.php">
Этот код говорит о том, что форма будет отправляться к скрипту enterToClubFormHandler.php.
Обновите страницу в Chrome и попробуйте заполнить форму и нажать "Поехали" - таким образом вы отправите форму.
Поздравляю, форма успешно отправилась на сервер. Ошибка 404 говорит о том, что скрипт enterToClubFormHandler.php на сервере не найден.
И, кстати, все отработало в точности, как мы описывали в nginx.conf на прошлом уроке - если файл не найден, то выводится ошибка 404.
Теперь давайте создадим файл enterToClubFormHandler.php в папке www.
Нажмите в браузере "Назад", обновите страницу и попробуйте отправить форму заново.
Теперь результат будет другим - пустая страница. Опять же, все верно: php-скрипт enterToClubFormHandler.php теперь есть, но он ничего не делает и ничего не выводит.
Давайте в скрипт, обрабатывающий форму, напишем следующий код:
<?php
print_r($_GET);
Напомню, <?php дает php-интерпретатору понять, что дальше идет php-код и его нужно обрабатывать.
print_r()
Функция, выводящая массив на экран (про массивы ниже расскажу).
$_GET - переменная, содержащая в себе массив, переданный на сервер в URL.
Передача данных GET-запросом
Обратите внимание на URL страницы (в адресной строке браузера)
http://127.0.0.1/enterToClubFormHandler.php?phone=%2B79998887766
С адресом до знака вопроса все ясно - это адрес нашего php-скрипта на нашем локальном сервере.
А дальше идут GET-параметры. А именно те, которые передаются с нашей формы. phone - номер телефона. Слово phone взято из атрибута name поля формы, которую мы делали. А значение взято из значения, которое ввел пользователь. %2B - таким образом в URL (адресе) передается символ плюса.
Знак вопроса отделяет URL скрипта от GET-параметров.
Давайте поэкспериментируем с GET-параметрами.
Вместо phone=%2B79998887766 напишем test=мой текст и нажмем Enter.
С одним параметром все ясно. Теперь разберемся, как передать больше параметров.
Параметры в URL друг от друга отделяются символом амперсанд - &.
Попробуем вот такой URL:
http://127.0.0.1/enterToClubFormHandler.php?test=мой текст¶m2=параметр 2param3=параметр 3
Вот именно таким образом на сервер передаются данные GET-запросом. То есть прямо в URL.
Поля формы тоже передаются параметром в запросе. Если будет больше 1 поля, то оно так и будет передано.
Обратите внимание, что данные, переданные в GET, в php на сервере хранятся в виде массива в переменной $_GET.
Передача данных POST-запросом
Итак, как передавать параметры запроса в адресной строке мы разобрались. Давайте теперь разберемся, как это делать не в адресной строке, а скрыто.
Для этого есть возможность передавать параметры не адресе, а в заголовке запроса.
Знакомьтесь с еще одним атрибутом тега form: атрибут method.
Он принимает всего 2 возможных значения: get или post. Собственно, от того, что выберите, данные формы будут передаваться либо одним способом, либо другим.
Кстати, если атрибут method не задан, то форма отправляет на сервер данные и так, и так: то есть сразу и post, и get.
Код формы должен стать таким:
<form action="enterToClubFormHandler.php" method="post">
Когда на сервер приходят данные с помощью POST, они сохраняются в php в массивом в переменной $_POST.
Поэтому в файле enterToClubFormHandler.php нам нужно изменить код $_GET на $_POST
print_r($_POST);
Теперь открываем заново главную страницу нашего сайта и заново отправляем форму.
По поводу возврата на предыдущую страницу стоит сказать пару слов:
Если вы жмете в браузере кнопку "Назад", то страница открывается в старой версии - она не загружается с веб-сервера заново. Поэтому, чтобы увидеть изменения в коде, ее нужно принудительно обновить.
Если вы открываете страницу не кнопками "назад-вперед" в браузере, то она должна прогрузиться с сервера заново. Но, если этого не произойдет, то попробуйте обновить страницу.
Бывает такая история, что страницу обновляешь, а код будто бы старый: старые стили, не отрабатывает новый код JS. Для этого нужно обновить страницу со сбросом кэша: на маке это CMD+Shift+R, на Windows это CTRL+F5.
Смотрим результат в браузере:
Обратите внимание, что в адресной строке все чисто - поля формы были переданы в POST. А в массиве $_POST на сервере все появилось и отобразилось на экран.
Передача данных на сервер с помощью Ajax
Два способа сверху всем хороши кроме того, что браузер открывает страницу, обрабатывающую запрос, а не остается на той же странице, говорит что-то вроде "спасибо за ваш интерес".
Чтобы отправлять данные на сервер без перезагрузки страницы, придумали Ajax.
Вот здесь уже мы увидим более четкое разделение Frontend и Backend.
Чтобы отправить данные со страницы без перезагрузки, придется снова окунуться в Javascript - ведь код на стороне браузера пишется на нем.
А серверная часть на php уже примет наш запрос, обработает и выдаст браузеру ответ.
Ответ сервера обработает Javascript и сделает определенные действия, как мы напишем.
Поехали.
В index.php к тегу формы нужно присвоить ID. Назовем его joinToClubForm
<form action="joinToClubFormHandler.php" method="post" id="joinToClubForm">
И, чтобы быть точнее в английском языке, давайте в action поменяем enterToClubFormHandler.php на joinToClubFormHandler.php и сам php-скрипт переименуем в joinToClubFormHandler.php .
На стороне html все - больше ничего не нужно делать.
В JS нас ждут серьезные изменения. В самый конец файла modals.js пишем:
document.getElementById("joinToClubForm").addEventListener("submit",sendJoinToClubFormHandler);//Обработчик события отправки формы
function sendJoinToClubFormHandler (e) {//Функция обработки отправки формы
e.preventDefault();//Останавливаем действие по умолчанию, то есть отправку формы на сервер.
let request = new XMLHttpRequest();//Создаем объект для Ajax-запроса
request.open(this.method, this.action, true);//Инициализируем запрос
request.onreadystatechange = function() {//Пишем обработчик ответа сервера
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {//Если статус ответа сервера успешный, то выполнится код внутри фигурных скобок
console.log(this);
}
}
let data = new FormData(this);//Записываем в переменную data данные формы.
request.send(data);//Отправляем запрос на сервер
}
document.getElementById("joinToClubForm").addEventListener("submit",sendJoinToClubFormHandler);
Мы уже ранее перехватывали события на странице. Здесь все точно также: перехватываем событие submit для нашей формы. Это событие отправки формы. При событии submit дальнейшая работа будет отправлена функции sendJoinToClubFormHandler()
По событиям есть документация. Рекомендую ознакомиться детально:
https://www.w3schools.com/tags/ref_eventattributes.asp
function sendJoinToClubFormHandler (e) {
Функция, которую мы сделали для обработки события отправки формы. В нее передается аргумент e - это событие отправки формы.
e.preventDefault();
Первое действие, которое выполняется в нашей функции - отмена действий отправки формы по умолчанию. То есть не будет открываться страница joinToClubFormHandler.php - браузер на нее переключаться не будет.
Как это работает: e - это объект события, который мы приняли в функцию аргументом. Отправлен он был автоматически с addEventListener.
preventDefault() - метод, доступный в объекте event.
let request = new XMLHttpRequest();
Этим кодом мы создаем переменную request и в нее записываем новый объект класса XMLHttpRequest. Этот класс предоставляет нам функциональность обмена данными с сервером.
О работе с переменными, о классах мы поговорим на следующих уроках. На данный момент нужно знать, что переменная объявляется ключевым словом var или let, экземпляр класса создается ключевым словом new.
request.open(this.method, this.action, true);
Так мы инициализируем наш запрос. Или открываем его. При инициализации нужно указать:
- метод передачи данных (POST),
- url скрипта, на который будет отправлен запрос
- и выбрать будет ли запрос выполняться синхронно или асинхронно.
Давайте теперь по порядку разбираться.
this
Это ключевое слово при работе с объектами. В нашем случае объект, с которым мы работаем - наша форма.
this.method
Хранит в себе значение атрибута method формы, а именно POST.
this.action
Хранит в себе значение атрибута action формы, а именно joinToClubFormHandler.php
Синхронно/асинхронно
Мы можем отправлять запрос на сервер синхронно: то есть отправили запрос, дождались, пока сервер обработал его, дал ответ и дальше мы уже продолжаем работу нашего JS-скрипта.
Можем отправлять асинхронно: то есть отправили запрос и работаем дальше, а когда сервер пришлет назад ответ, мы уже этот ответ обработаем.
true/false
Соответственно, если хотим асинхронно - пишем true, если синхронно - false. Мы хотим асинхронно.
request.onreadystatechange = function() {
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { console.log(this);
}
}
Здесь мы описываем как раз обработчик ответа от сервера.
request - объект нашего запроса.
onreadystatechange - обработчик события, когда на сервере запрос обработается.
На это событие мы вешаем функцию, тело которой будет выполняться.
if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
Условие, если текущее состояние ответа сервера (this.readyState) соответствует состоянию XMLHttpRequest.DONE и сервер вернул статус (this.status) 200 (200 - это успешная обработка, 500 - это ошибка сервера, 404 - это если страница не найдена. Кодов ответов много. Нас сейчас интересует либо успех 200, либо все остальное).
Если это условие соответствует действительности, то будет выполнен код внутри фигурных скобок.
console.log(this);
В консоль браузера будет возвращен объект, в котором вся информация о запросе. Мы узнаем, что ответил сервер.
let data = new FormData(this);
Создаем новую переменную data и записываем в нее данные, которые пользователь ввел в форму. Делаем это путем создания нового экземпляра класса FormData, в конструктор которого передаем аргумент this - это объект нашей формы.
Мы, когда будем подробнее разбираться с переменными, классами, областями видимости и контекстом, разберемся, что такое this и как оно работает. Важно понимать, что внутри функции sendJoinToClubFormHandler, которая была вызвана событием отправки формы, this - это объект формы, а события запроса request при ответе сервера, this - это объект запроса.
request.send(data);
Вызываем метод send() объекта request (наш запрос) и передаем ему в качестве аргумента переменную data - в ней хранятся данные с заполненной формы.
Вот такой код получился.
Давайте теперь нашу страницу обновим в браузере со сбросом кэша (CTRL+F5 в Windows, CMD+Shift+R в Mac).
Теперь откроем инструменты разработчика (правая панель) и там откроем вкладку "Консоль".
Настало время попробовать отправить форму и посмотреть на результат.
Браузер отправил запрос, сервер его обработал и ответил нам. После чего отработал код console.log(this);, который вывел в консоль информацию об объекте запроса.
Слева от текста XMLHttpRequest есть треугольник - нажмите на него, чтобы развернуть информацию о запросе.
Мы видим все свойства, которые есть у этого объекта. Например, там есть свойство response - это тело ответа сервера. То есть, чтобы вывести в консоль не весь объект запроса, а только ответ сервера, можно написать this.response. Давайте это и сделаем.
console.log(this); меняем на console.log(this.response);
Заново обновляем страницу со сбросом кэша (CTRL+F5 в Windows, CMD+Shift+R в Mac) и отправляем форму.
Смотрим результат в консоли.
Это в точности то, что нам отвечал сервер, когда при отправке формы открывался php-скрипт обработки формы. А теперь мы не меняем страницу, а отправляем форму с помощью Ajax.
Отлично. С Ajax познакомились. Как отправлять данные с браузера на сервер посмотрели.
Скорее всего будут вопросы. Не стесняйтесь спрашивать в телеграм-группе:
Доработаем код на php
Давайте немного изменим код на PHP.
У нас есть массив $_POST - в нем хранятся данные, переданные скрипту с помощью POST.
В них есть "phone" - телефон из нашей формы.
print_r() просто выводит на экран все содержимое массива вместе с ключами и значениями.
То есть phone - это ключ, а номер телефона внутри - это значение.
Чтобы достать только phone, нужно написать $_POST["phone"].
Давайте заменим наш код joinToClubFormHandler.php на:
<?php
echo "Новый желающий вступить в клуб: ",$_POST["phone"];
Обновляем страницу со сбросом кэша и заново отправляем форму.
Ура. На этом пока все. Дальше мы с этими данными сделаем 2 вещи: сохраним в базу данных и отправим уведомление в телеграм.
Для базы данных создадим новый контейнер mysql в докере.
А для телеграм создадим нового телеграм-бота.
Результат выполнения урока можно скачать здесь:
https://github.com/madmaker/srmt22-lsn15/archive/refs/tags/v1.zip
Резюме урока
Поздравляю, мы познакомились с тем, как отправлять данные из браузера на сервер и принимать ответы. Посмотрели, как работает POST, GET, как отправлять данные с помощью Ajax.
Посмотрели, как принимать данные от браузера на сервере с помощью php и как отвечать браузеру.
Всем спасибо!
Что делать дальше и как нам помочь?
1. Поставить лайк
2. Написать в комментарии, за сколько времени удалось сделать урок, с чем возникли сложности, что бы хотелось разобрать более детально
3. Подписаться на этот канал
4. Добавиться к нам в группу в наш открытый клуб веб-разработки в телеграм: https://t.me/srmt22_webclub
5. Сделать репост этого урока себе в соцсети.
Что будет на следующем занятии?
Создадим контейнер базы данных MySQL в Docker и разберемся, как записывать результаты заполнения формы о вступлении в клуб в базу данных.
Трудоустройство
Те, кто хочет индивидуальное обучение с преподавателем и быстрое гарантированное трудоустройство, пишите нам в телеграм: