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

Форма связи с аттачем

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

  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|max:100',
            'email' => 'required|email|max:100',
            'message' => 'required|max:500',
            '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;
        $data->ext = $ext ?? null;
        // В метод Mail::to первым аргументом передаем почту куда будет отправка письма, вторым методом передаем класс отправки почты в который передаем аргументом объект с данными формы $data который попадет в конструктор класса FeedbackMailer
        Mail::to($data->email)->send(new FeedbackMailer($data));
        // Редирект через имя роута на ту же страницу с формой
        return redirect()->route('feedback.index')
            // Вывод сообщения
            ->with('success', 'Ваше сообщение успешно отправлено');
    }
}

Роуты

Добавляем два маршрута:

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@extends('layouts.app')
@section('content')
    <h1>Обратная связь</h1>
    @if (session('success'))
        <div class="alert alert-success" role="alert">
            {{ session('success') }}
        </div>
    @endif
    @if ($errors->any())
        <div class="alert alert-danger" role="alert">
            <ul>
            @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
            @endforeach
            </ul>
        </div>
    @endif
    <form 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" value="{{ old('name') ?? '' }}">
        </div>
        <div class="form-group">
            <input type="email" class="form-control" name="email" placeholder="Адрес почты"
                    required maxlength="100" value="{{ old('email') ?? '' }}">
        </div>
        <div class="form-group">
        <textarea class="form-control" name="message" placeholder="Ваше сообщение"
                    required maxlength="500" rows="3">{{ old('message') ?? '' }}</textarea>
        </div>
        <div class="form-group">
            <input type="file" class="form-control-file" name="image" accept="image/png, image/jpeg">
        </div>
        <div class="form-group">
            <button type="submit" class="btn btn-primary">Отправить</button>
        </div>
    </form>
@endsection

Шаблон почтового сообщения

Нам потребуется шаблон для письма:

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\Support\Facades\Storage;
use stdClass;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class FeedbackMailer extends Mailable {
    use Queueable, SerializesModels;

    // Публичная переменная в которую запишем переданные данные формы из контроллера FeedbackController
    public $data;

    // Плучаем данные формы из контроллера FeedbackController
    public function __construct(stdClass $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(stdClass $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(stdClass $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.
Спасибо, ваш запрос принят и будет обработан!
Эйч Маркетинг