Полный цикл в digital

Форма связи AJAX

Отправку почты рассмотрим на примере формы обратной связи с полями Имя, Почта, Сообщение.

  1. Создаем контроллер, который будет показывать фому и обрабатывать POST-запрос от клиента
  2. Добавляем два роута
  3. Создаем форму
  4. Создаем шаблон письма
  5. Добавляем класс FeedbackMailer, расширяющий Illuminate\Mail\Mailable
  6. Настраиваем 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');
        });
    }
}
Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг