Laravel. Отправка письма из приложения

18.11.2020

Теги: LaravelPHPPOSTWeb-разработкаКлассТеорияФормаФреймворк

Отправку почты рассмотрим на примере формы обратной связи с полями «Имя», «Почта» и «Сообщение». Нам потребуется создать контроллер, который будет показывать фому и обрабатывать POST-запрос от клиента. Потом добавим два роута и создам шаблон с формой. После этого нам потребуется создать класс FeedbackMailer, расширяющий Illuminate\Mail\Mailable и покопаться в настройках Laravel.

Форма обратной связи

Создаем контроллер FeedbackController:

> php artisan make:controller FeedbackController
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',
        ]);

        $data = new stdClass();
        $data->name = $request->name;
        $data->email = $request->email;
        $data->message = $request->message;
        Mail::to($data->email)->send(new FeedbackMailer($data));
        return redirect()->route('feedback.index')
            ->with('success', 'Ваше сообщение успешно отправлено');
    }
}

Добавляем два маршрута в файл routes/web.php:

Route::get('/feedback', 'FeedbackController@index')->name('feedback.index');
Route::post('/feedback', 'FeedbackController@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') }}">
        @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">
            <button type="submit" class="btn btn-primary">Отправить</button>
        </div>
    </form>
@endsection

Класс отправки почты

В Laravel каждый тип почтового сообщения (обратная связь, заказ в магазине), отправляемых приложением, представлен классом Mailable. Эти классы хранятся в директории app/Mail, которая будет создана при создании первого такого класса.

> php artisan make:mail FeedbackMailer
namespace App\Mail;

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;

    private $data;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(stdClass $data) {
        $this->data = $data;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build() {
        return $this->from('noreply@aurora.com', 'ООО ТД АВРОРА')
            ->subject('Форма обратной связи')
            ->view('email.feedback', ['data' => $this->data]);
    }
}

Чтобы не указывать заголовок письма From (от кого), можно задать это в файле .env:

APP_NAME="ООО ТД АВРОРА"
MAIL_FROM_ADDRESS=noreply@aurora.com
MAIL_FROM_NAME="${APP_NAME}"

Эти три значения используются далее в файле конфигурации отправки почты config/mail.php:

return [
    /* ... */
    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'noreply@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],
    /* ... */
];
class FeedbackMailer extends Mailable {
    /* ... */
    public function build() {
        return $this->subject('Форма обратной связи')->view('email.feedback', ['data' => $this->data]);
    }
}

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

Еще нам потребуется шаблон для письма 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>

Отправка почты в log-файл

Теперь настройки Laravel для отправки писем. Для начала будем записывать письма просто в log-файл, для этого редактируем файл .env:

MAIL_MAILER=log

Эта настройка используется в файле конфигурации config/mail.php, где можно задать, с помощью какого mailer-а будут отправляться письма:

return [

    /*
     * Default Mailer
     */
    'default' => env('MAIL_MAILER', 'smtp'),

    /*
     * Mailer Configurations
     */
    'mailers' => [
        'smtp' => [
            'transport' => 'smtp',
            'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
            'port' => env('MAIL_PORT', 587),
            'encryption' => env('MAIL_ENCRYPTION', 'tls'),
            'username' => env('MAIL_USERNAME'),
            'password' => env('MAIL_PASSWORD'),
            'timeout' => null,
            'auth_mode' => null,
        ],

        'ses' => [
            'transport' => 'ses',
        ],

        'mailgun' => [
            'transport' => 'mailgun',
        ],

        'postmark' => [
            'transport' => 'postmark',
        ],

        'sendmail' => [
            'transport' => 'sendmail',
            'path' => '/usr/sbin/sendmail -bs',
        ],

        'log' => [
            'transport' => 'log',
            'channel' => env('MAIL_LOG_CHANNEL'),
        ],

        'array' => [
            'transport' => 'array',
        ],
    ],

    /*
     * Global From: Address
     */
    'from' => [
        'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
        'name' => env('MAIL_FROM_NAME', 'Example'),
    ],

    /*
     * Markdown Mail Settings
     */
    'markdown' => [
        'theme' => 'default',

        'paths' => [
            resource_path('views/vendor/mail'),
        ],
    ],

];

Теперь отправленные письма будут записываться в log-файл, где их можно посмотреть:

Но не очень удобно, что письма записываются в один файл с сообщениями об ошибках. Так что создадим еще одну настройку в файле .env:

MAIL_LOG_CHANNEL=maillog

И добавим еще один канал (channel) в файле конфигурации логов приложения config/logging.php:

return [
    'channels' => [
        /* ... */
        'maillog' => [
            'driver' => 'single',
            'path' => storage_path('mails/laravel.log'),
        ],
    ],
];

Теперь письма будут все так же записываться в лог-файл, но в директорию storage/mails.

Отправка через smtp-сервер

Отправлять будем через smtp-сервер Яндекса, для этого нужно получить пароль приложения здесь:

После этого редактируем файл конфигурации приложения .env:

APP_NAME="ООО ТД АВРОРА"
MAIL_MAILER=smtp
MAIL_HOST=smtp.yandex.ru
MAIL_PORT=465
MAIL_USERNAME=your_name@yandex.ru
MAIL_PASSWORD=zsfmsbxwdnbuanjk
MAIL_ENCRYPTION=ssl
MAIL_FROM_ADDRESS=your_name@yandex.ru
MAIL_FROM_NAME="${APP_NAME}"

Файл вложения

Давайте добавим на форму еще одно поле для загрузки изображения и будем это изображение прикреплять к письму:

@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
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();
        $data->name = $request->name;
        $data->email = $request->email;
        $data->message = $request->message;
        $data->image = $raw ?? null;
        $data->ext = $ext ?? null;
        Mail::to($data->email)->send(new FeedbackMailer($data));
        return redirect()->route('feedback.index')
            ->with('success', 'Ваше сообщение успешно отправлено');
    }
}
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;

    public $data;

    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->subject('Форма обратной связи')
            ->view('email.feedback');
    }
}

Мы получаем из объекта http-запроса сырую строку файла и передаем классу FeedbackMailer. Но можно еще прикрепить файл из хранилища с помощью методов attachFromStorage() (используется диск по умолчанию) или attachFromStorageDisk() (диск надо указать самостоятельно).

class 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', 'Ваше сообщение успешно отправлено');
    }
}
class 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).

class 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:

class 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 перед отправкой сообщения. Это дает возможность кастомизировать сообщение перед тем как оно будет отправлено.

class FeedbackMailer extends Mailable {
    /* ... */
    public function build() {
        $this->subject('Форма обратной связи')
            ->view('email.feedback');
        $this->withSwiftMessage(function ($message) {
            $message->getHeaders()->addTextHeader('Custom-Header', 'HeaderValue');
        });
    }
}

Поиск: Laravel • PHP • POST • Web-разработка • Класс • Форма • Фреймворк • Письмо • Mailable • Теория

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.