Форма связи AJAX
Отправку почты рассмотрим на примере формы обратной связи с полями Имя
, Почта
, Сообщение
.
- Создаем контроллер, который будет показывать фому и обрабатывать POST-запрос от клиента
- Добавляем два роута
- Создаем форму
- Создаем шаблон письма
- Добавляем класс
FeedbackMailer
, расширяющийIlluminate\Mail\Mailable
- Настраиваем Laravel
Контроллер
Создаем контроллер FeedbackController
:
php artisan make:controller FeedbackController
app/Http/FeedbackController.php<?php
namespace App\Http\Controllers;
use stdClass;
use App\Mail\FeedbackMailer;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
class FeedbackController extends Controller
{
// Выводим форму на странице
public function index()
{
return view('feedback.index');
}
// Отправляем данные формы на почту
public function send(Request $request)
{
// Валидация полей формы
$request->validate([
'name' => 'required',
'email' => 'required',
'message' => 'required',
'image' => 'mimes:jpeg,jpg,png|max:5000',
]);
// Переменная с файлом
$image = $request->file('image');
// Проверка загрузки файла
if ($image) {
// Название файла
$raw = $image->get();
// Расширение файла
$ext = $image->extension();
}
// Создаем динамический объект
$data = new stdClass();
// Данные из поля формы name
$data->name = $request->name;
// Данные из поля формы email
$data->email = $request->email;
// Данные из поля формы message
$data->message = $request->message;
// Тернарная проверка названия файла на null
$data->image = $raw ?? null;
// Тернарная проверка расширения файла на null
$data->ext = $ext ?? null;
// В метод Mail::to первым аргументом передаем почту куда будет отправка письма, вторым методом передаем класс отправки почты в который передаем аргументом объект с данными формы $data который попадет в конструктор класса FeedbackMailer
Mail::to($data->email)->send(new FeedbackMailer($data));
// Возвращаем в AJAX запрос json строку, через return можно вернуть что угодно, тут же можно выполнить проверку
return response()->json(['success' => 'Contact form submitted successfully']);
}
}
Роуты
Добавляем два маршрута:
routes/web.phpRoute::get('/feedback', [FeedbackController::class, 'index'])->name('feedback.index');
Route::post('/feedback', [FeedbackController::class, 'send'])->name('feedback.send');
Форма связи
Создаем шаблон вместе со скриптом:
resources/views/feedback/index.blade.php<h1>Обратная связь</h1>
<form id="contactform" method="post" action="{{ route('feedback.send') }}" enctype="multipart/form-data">
@csrf
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Имя, фамилия" required maxlength="100">
<div class="error_name"></div>
</div>
<div class="form-group">
<input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="100">
<div class="error_email"></div>
</div>
<div class="form-group">
<textarea class="form-control" name="message" placeholder="Ваше сообщение" required maxlength="500" rows="3"></textarea>
<div class="error_message"></div>
</div>
<div class="form-group">
<input type="file" class="form-control-file" name="image" accept="image/png, image/jpeg">
<div class="error_image"></div>
</div>
<div class="form-group">
<button id="btn" type="submit" class="btn btn-primary">Отправить</button>
</div>
</form>
<script>
// Находим кнопку отправки формы, отлавливаем клик, запускаем функцию AJAX
document.querySelector("#btn").addEventListener("click", function(event) {
ajaxForm();
event.preventDefault();
});
function ajaxForm() {
// Форма
let form = document.querySelector('#contactform');
// Поля формы
let token = form.querySelector('input[name="_token"]').value;
let name = form.querySelector('input[name="name"]').value;
let email = form.querySelector('input[name="email"]').value;
let message = form.querySelector('textarea[name="message"]').value;
let image = form.querySelector('input[name="image"]').value;
// Предупреждения формы
let errorName = form.querySelector('.error_name');
let errorEmail = form.querySelector('.error_email');
let errorMessage = form.querySelector('.error_message');
let errorImage = form.querySelector('.error_image');
//debugger;
fetch("/feedback", {
headers: {
"Content-Type": "application/json",
"Accept": "application/json, text-plain, */*",
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-TOKEN": token
},
method: 'post',
credentials: "same-origin",
body: JSON.stringify({
name: name,
email: email,
message: message,
image: image
})
})
.then((res) => {
return res.json();
})
.then((data) => {
data.errors.name ? errorName.innerHTML = data.errors.name[0] : errorName.innerHTML = "";
data.errors.email ? errorEmail.innerHTML = data.errors.email[0] : errorEmail.innerHTML = "";
data.errors.message ? errorMessage.innerHTML = data.errors.message[0] : errorMessage.innerHTML = "";
data.errors.image ? errorImage.innerHTML = data.errors.image[0] : errorImage.innerHTML = "";
})
.catch(function(error) {
console.log(error);
});
}
</script>
Шаблон почтового сообщения
Нам потребуется шаблон для письма:
resources/views/email/feedback.blade.php<h1>Форма обратной связи</h1>
<p><strong>Имя:</strong> {{ $data->name }}</p>
<p><strong>Почта:</strong> {{ $data->email }}</p>
<p><strong>Сообщение:</strong> {{ $data->message }}</p>
Класс отправки почты
В Laravel каждый тип почтового сообщения (обратная связь, заказ в магазине), отправляемых приложением, представлен классом Mailable
. Эти классы хранятся в директории app/Mail
, которая будет создана при создании первого такого класса.
Создаем класс:
php artisan make:mail FeedbackMailer
Заполняем класс:
app/Mail/FeedbackMailer.php<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class FeedbackMailer extends Mailable
{
use Queueable, SerializesModels;
// Публичная переменная в которую запишем переданные данные формы из контроллера FeedbackController
public $data;
// Плучаем данные формы из контроллера FeedbackController
public function __construct($feedback)
{
$this->data = $feedback;
}
// Создаем сообщение
public function build()
{
if ($this->data->image) {
$this->attachData($this->data->image, 'image.' . $this->data->ext);
}
// От кого письмо
$this->from('noreply@aurora.com', 'ООО ТД АВРОРА')
// Тема письма
->subject('Форма обратной связи')
// Вызываем представление
->view('email.feedback');
}
}
Мы получаем из объекта http-запроса сырую строку файла и передаем классу FeedbackMailer
. Но можно еще прикрепить файл из хранилища с помощью методов attachFromStorage()
(используется диск по умолчанию) или attachFromStorageDisk()
(диск надо указать самостоятельно).
app/Http/FeedbackController.phpclass FeedbackController extends Controller {
public function send(Request $request) {
$image = $request->file('image');
if ($image) { // был загружен файл изображения
$path = $image->store('feedback', 'local');
}
$data = new stdClass();
$data->image = $path ?? null;
Mail::to($data->email)->send(new FeedbackMailer($data));
return redirect()->route('feedback.index')
->with('success', 'Ваше сообщение успешно отправлено');
}
}
app/Mail/FeedbackMailer.phpclass FeedbackMailer extends Mailable {
public function build() {
if ($this->data->image) {
$this->attachFromStorageDisk('local', $this->data->image);
}
$this->subject('Форма обратной связи')
->view('email.feedback');
}
}
Мы сохраняем загруженное изображение в директорию storage/app/feedback
и потом оттуда прикрепляем к письму. Смысла хранить изображение дальше нет, так что его можно удалить. Но сразу удалять нельзя — возникает ошибка, потому что файл удаляется до того, как будет прикреплен к письму. Нужно добавить обработчик события MessageSent
(возникает после отправки) — и уже там удалить файл с диска.
Вместо заключения
Передать данные в шаблон письма resources/views/email/feedback.blade.php
можно через публичное свойство класса FeedbackMailer
. Все публичные поля этого класса будут доступны в шаблоне как переменные, в нашем случае $data
:
app/Mail/FeedbackMailer.phpclass FeedbackMailer extends Mailable {
use Queueable, SerializesModels;
public $data;
public function __construct($data) {
$this->data = $data;
}
public function build() {
return $this->subject('Форма обратной связи')->view('email.feedback');
}
}
Чтобы изменить данные, прежде чем передать их шаблону, свойство должно быть объявлено как protected
или private
:
app/Mail/FeedbackMailer.phpclass FeedbackMailer extends Mailable {
use Queueable, SerializesModels;
private $data;
public function __construct($data) {
$this->data = $data;
}
public function build() {
return $this->subject('Форма обратной связи')
->view('email.feedback')
->with([ // в шаблоне будут доступны $name, $email, $message
'name' => $this->data->name,
'email' => $this->data->email,
'message' => $this->data->message,
]);
}
}
Метод withSwiftMessage()
класса Mailable
позволяет зарегистрировать анонимную функцию, которая будет вызываться экземпляром класса SwiftMailer
перед отправкой сообщения. Это дает возможность кастомизировать сообщение перед тем как оно будет отправлено:
app/Mail/FeedbackMailer.phpclass FeedbackMailer extends Mailable {
public function build() {
$this->subject('Форма обратной связи')
->view('email.feedback');
$this->withSwiftMessage(function ($message) {
$message->getHeaders()->addTextHeader('Custom-Header', 'HeaderValue');
});
}
}