Новый релиз Laravel 5.5 станет релизом долгосрочной поддержки (Long Term Support, LTS). Это значит, что он будет получать правки багов в течение 2х лет, а обновления безопасности — в течение 3х. Таким же был релиз Laravel 5.1, но его двухгодичный срок правок подходит к концу в этом году. Давайте посмотрим без лишней суеты, что нас ждет в новой версии.
Создаем новый проект на Laravel 5.5
Поскольку новый релиз еще не выпущен официально, мы можем пока скачать dev-релиз при помощи следующей команды:
laravel new laravel55 --dev
cd laravel55
php artisan key:generate
Если вы не любите Laravel installer, можете воспользоваться командой composer:
composer create-project --prefer-dist --stability=dev laravel/laravel:dev-master
cd laravel
php artisan key:generate
Когда мы зайдем на домашнюю страницу нового приложения, мы должны будем увидеть welcome page, аналогичную той, что была в предыдущих версиях Laravel.
Рендерим Mailables в браузере
Мне кажется, эта фича будет очень полезной. В предыдущих версиях Laravel нам приходилось слать реальные письма или пользоваться email-клиентом вроде Mailtrap, чтобы протестировать рассылку, и задачка это была не самая веселая. Больше не надо страдать, в Laravel 5.5 теперь можно отрендерить шаблон письма прямо в браузере.
Вот быстрый способ, как это сделать: создаем новый mailable и шаблон письма для нашего проекта:
php artisan make:mail Welcome --markdown=emails.welcome
Я предпочитаю использовать формат markdown, поскольку мы получаем шаблон уже с каким-то контентом. Открываем файл web.php
и создаем тестовый route, чтобы проверить шаблон письма:
Route::get('/email', function () {
return new App\Mail\Welcome();
});
routes/web.php
Если мы зайдем в route /email, то мы сможем увидеть шаблон письма:
На самом деле происходит следующее. В Laravel 5.5 класс ‘Mailable’ использует контракт ‘Renderable’ с методом ‘render()’. Вот реализация метода render в ‘Illuminate/Mail/Mailable.php’:
public function render()
{
Container::getInstance()->call([$this, 'build']);
return Container::getInstance()->make('mailer')->render(
$this->buildView(), $this->buildViewData()
);
}
Illuminate/Mail/Mailable.php
С помощью этого метода можно получить view. Если бы мы попытались вернуть экземпляр класса, который не применяет контракт ‘Renderable’ внутри наших route’ов, то мы бы получили ‘UnexpectedValueException’.
Кастомное оформление писем
Если использовать Markdown для оформления писем, то Laravel поставит дефолтную тему. Впрочем, кому-то могут потребоваться собственные стили, чтобы продвигать свой бренд.
Чтобы сделать кастомное оформление для конкретного mailable, сначала создаем кастомный файл ‘.css’ с нужными нам стилями:
touch resources/views/vendor/mail/html/themes/custom.css
Затем указываем имя этого файла в качестве характеристики класса Mailable:
class Welcome extends Mailable
{
protected $theme = 'custom';
[...]
}
app/Mail/Welcome.php
Таким образом, шаблон письма будет основываться на тех стилях, которые мы указали в файле custom.css
. Что особенно радует, так это то, что мы можем задавать разные стили для разных mailable`ов.
Вспомогательные функции исключений
В Laravel 5.5 входят две вспомогательные функции исключений которые помогут нам создавать более выразительный код. Это методы throw_if
и throw_unless
. В оба включено по три аргумента, причем третий необязателен.
Посмотрим, как можно применять эти исключения:
$number = 2;
throw_if($number !== 3, new NotThreeException('Number is not three'));
// or
throw_if($number !== 3, NotThreeException::class, 'Number is not three');
Если использовать вспомогательную функцию ‘throw_if’, исключение будет выброшено при условии, что первый аргумент будет расценен как true.
Если использовать throw_unless
, то по сути будет все то же самое, с тем лишь отличием, что исключение будет выброшено, если первый аргумент окажется false.
$number = 2;
throw_unless($number === 3, new NotThreeException('Number is not three'));
// or
throw_unless($number === 3, NotThreeException::class, 'Number is not three');
Не лучший пример, но для демонстрации подойдет.
Команда migrate:fresh
Вы, вероятно, оказывались в ситуации, когда вам приходилось восстанавливать базу данных. В предыдущих версиях Laravel это можно было сделать при помощи команды php artisan migrate:refresh
. Команда migrate:refresh
откатывает назад все миграции, основываясь на том, что описывается в ‘down’ методе каждого миграционного файла, а затем запускает миграции снова.
Но у вас, возможно, пару раз случались проблемы с этой командой, особенно при работе с ограничениями внешнего ключа (foreign key constraints) или когда один из ‘down()’ методов в какой-либо из ваших миграций не был определен в достаточной мере. Когда такое происходит, мы в большинстве случаев вручную избавляемся от таблицы, с которой возникают проблемы — (возможно, используя CLI или какой-нибудь GUI). Вот именно тогда команда migrate:fresh
спешит на помощь. Эта команда удаляет все таблицы, а затем снова запускает существующие миграции:
Stack Trace ошибок в формате JSON
Не самое большое изменение, но в предыдущих версиях Laravel мы видели HTML-разметку от клиента API вроде Postman каждый раз, когда возникала ошибка в наших API. В Laravel 5.5 в случае ошибки мы получаем не HTML-разметку, а трассировку в JSON, которую легче воспринимать:
Автоматическая установка пакетов
Чтобы использовать сторонний пакет в наших проектах на Laravel, мы делаем следующее:
- Устанавливаем пакет.
- Регистрируем сервис-провайдер пакета.
- Регистрируем фасады, если они есть.
Как видите, процедуру можно упростить. Теперь будет следующим образом.
С автоматической установкой пакетов мы просто выбираем нужный пакет и ставим его на лету. Замечу, впрочем, что это возможно, только если поставщик пакета настроил его соответствующим образом.
Если мы посмотрим на пакет Laravel Debugbar, для которого уже настроена автоустановка, мы увидим, что внутри файла composer.json
есть секция extra
:
"extra": {
"laravel": {
"providers": [
"Foo\\Bar\\ServiceProvider"
],
"aliases": {
"Bar": "Foo\\Bar\\Facade"
}
}
}
Поставщикам пакетов надо будет добавить секцию extra в файл composer.json
, а затем указать сервис-провайдеров и любые алиасы для пакета.
Еще один плюс автоустановки пакетов в том, что если удалить зависимость, то ничего не сломается. Обычно даже после сноса пакета его сервис-провайдеры и фасады все еще торчат в файле config/app.php
, и в некоторых случаях из-за них могут возникать ошибки.
С автоустановкой, если удалить пакет через Composer, то удаляется также все, что связано с этим пакетом.
Изменения в команде vendor:publish
В предыдущих версиях Laravel команда vendor:publish
публиковала ресурсы всех пакетов и самого фреймворка. Некоторые из этих ресурсов включают миграции, views и конфиги.
В Laravel 5.5 надо уточнять в деталях, что именно мы хотим опубликовать при помощи этой команды. Если мы запускаем команду php artisan vendor:publish
без каких-либо флагов, от нас потребуется выбрать выбрать провайдера или тег, чтобы было проще опубликовать только то, что мы хотим. См. скриншот ниже:
Мы можем обойти этот шаг, если укажем метку --all
или --provider
при запуске команды publish
:
php artisan vendor:publish --all
Разнообразие front-end пресетов
В Laravel 5.3 и 5.4 у нас по умолчанию были некоторые заготовки Vue и Bootstrap, что упрощало front-end разработку. В новой версии в этот набор добавили React. Впрочем, он не стоит там по умолчанию.
Новая команда artisan помогает управлять front-end пресетами. У нас будет только та заготовка, которая нам нужна для того пресета, с которым мы хотим работать. Но не всех устраивают дефолтные пресеты, Vue, Bootstrap и React, кому-то может потребоваться что-то еще. Возможно, другой front-end фреймворк. И Laravel уже позаботился об этом:
php artisan preset none
Эта команда удалит все существующие front-end заготовки. Если бы мы захотели использовать React, следующая команда поможет нам с заготовкой:
php artisan preset react
Ниже представлена эта новая команда в действии:
Whoops вернулся!
В Laravel 5.5 вернулся Whoops! с новым способом отображения ошибок. Теперь, если при разработке есть ошибка, мы сможем увидеть эту строчку кода в виде скриншота вместе с сообщением об ошибке. На мой взгляд, сообщение об ошибке теперь выглядит лучше, а то, что мы теперь получаем скриншот с ошибочной строчкой кода, позволяет исправлять ошибки ещё проще.
Пример ошибки с Whoops:
Еще одна крутая фишка в том, что теперь Whoops позволяет открывать указанные файлы напрямую в вашем IDE или редакторе. Эта функция работает только в том случае, если у вас есть локальный доступ к PHP-файлам на той машине, на которые установлен редактор. Чтобы ее настроить, откройте app/Exceptions/Handler.php и добавьте вот этот сниппет:
[...]
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Arr;
use Whoops\Handler\PrettyPageHandler;
[...]
class Handler extends ExceptionHandler
{
[...]
protected function whoopsHandler()
{
return tap(new PrettyPageHandler, function ($handler) {
$files = new Filesystem;
$handler->setEditor('sublime');
$handler->handleUnconditionally(true);
$handler->setApplicationPaths(
array_flip(Arr::except(
array_flip($files->directories(base_path())), [base_path('vendor')]
))
);
});
}
}
app\Exceptions\Handler.php
Этот сниппет отменяет метод whoopsHandler()
основного класса при помощи строчки $handler->setEditor('sublime')
, благодаря которой ссылка открывается в Sublime Text. Если вы используете другой редактор, посмотрите статью на гитхабе со списком всех поддерживаемых редакторов и инструкцией, как добавить свой собственный. Если у вас Mac, не забудьте скачать протокол sublime URL для этой работы.
Метод Report кастомных исключений
В предыдущих версиях если мы хотели настроить кастомные исключения каким-то особым образом, нам приходилось размещать их внутри метода report в файле Handler.php. Например так:
[...]
public function report(Exception $exception)
{
if ($exception instanceof CustomException) {
// Do something
}
if ($exception instanceof MyOtherException) {
// Do something
}
if ($exception instanceof MyOtherCustomException) {
// Do something
}
return parent::report($exception);
}
[...]
app/Exceptions/Handler.php
Если у нас есть, например, 50 основных исключений, то этот файлик превратится в нечто ужасное. В Laravel 5.5, для того чтобы указать, что происходит в случае кастомного исключения, можно создать report()
метод внутри исключения:
[...]
class CustomException extends \Exception
{
public function report()
{
// send email
}
}
[...]
app/Exceptions/CustomException.php
Генераторы фабрик моделей
В Laravel 5.5 появилась новая команда для создания фабрики моделей. Фабрика моделей очень удобна, когда нам нужно сгенерить фейковые данные или новый объект для тестов.
Чтобы создать фабрику для конкретного класса, запускаем команду:
php artisan make:factory Post
Теперь если мы откроем database/factories, то увидим класс PostFactory:
[...]
$factory->define(App\Post::class, function (Faker $faker) {
return [
//
];
});
database/factories/PostFactory.php
Я считаю, что это более элегантный подход, поскольку мы разделяем ответственности. В предыдущих версиях Laravel все фабрики размещались внутри одного файла app/factories/ModelFactory.php
.
Возврат валидированных данных
Теперь стало возможным получить данные из валидатора и передать их в метод create
. В предыдущих версиях Laravel мы создавали новые объекты так:
{
$this->validate(request(), [
'title' => 'required',
'body' => 'required'
]);
// return Post::create(request()->only(['title', 'body'])); or
return Post::create(request()->all());
}
В Laravel 5.5 теперь можно создавать объекты напрямую из валидированных данных:
public function store()
{
$post = $this->validate(request(), [
'title' => 'required',
'body' => 'required'
]);
return Post::create($post);
}
Также можно вызвать команду validate
прямо из request’а:
public function store()
{
$post = request()->validate([
'title' => 'required',
'body' => 'required'
]);
return Post::create($post);
}
Замечу, впрочем, что нужно соблюдать осторожность, когда вы создаете объекты таким способом, поскольку любой атрибут, который вы оставите вне метода валидации, не будет иметь значения. Чтобы справиться с этой проблемой, мы передаем все атрибуты, которые мы хотим создать внутри метода валидации для данного объекта, даже если их значения не требуют никакой валидации:
$post = request()->validate([
'title' => 'required',
'body' => 'required',
'notRequiredField' => '',
]);
return Post::create($post);
Таким образом, это поле автоматически добавляется к разрешенным данным запроса, но оно не ограничивается никакими правилами валидации.
Кастомные правила валидации
В предыдущих версиях Laravel их можно было задать при помощи метода Validator::extend
. Но не было централизации. Мы добавляли правило в файл AppServiceProvider
, а затем сообщение в файл inside the resources/lang/en/validation.php
. В документации Laravel подробно описано, как это делается в версии 5.4.
В Laravel 5.5 у нас есть новая команда artisan, которая определяет кастомную валидацию. Эта команда создает новый класс, реализующий контракт Rule
. Создадим новое правило, чтобы посмотреть, что у него внутри:
php artisan make:rule CustomRule
Если заглянуть в app/Rules/CustomRule.php
, то мы увидим два метода — метод passes
и метод message
. Метод passes
берет 2 параметра, т.е. attribute
и value
, и возвращает boolean. Если вы запутались, $attribute
— это поле, которое нужно валидировать, а $value
— это реальное значение, которое передается атрибуту.
Предположим, например, что мы не хотим давать никакого конкретного имени пользователю. Тогда наше правило будет выглядеть так:
{
[...]
public function passes($attribute, $value)
{
return $value !== 'unwantedname';
}
public function message()
{
return 'You cannot use that as your username';
}
[...]
}
app/Rules/CustomRule.php
Затем мы используем новое правило, чтобы валидировать атрибут username
:
use App\Rules\CustomRule;
request()->validate([
'username' => [
'required',
new CustomRule()
],
'anotherfield' => 'required|min:5'
]);
app/Rules/CustomRule.php
Как определяется кастомная валидация в новой версии Laravel, подробно описано в статье Taylor Otwell.
В коллекции добавлены DD и Dump
Теперь в коллекции включены как метод dump(), так и dd(). В предыдущих версиях Laravel, когда мы отлаживали коллекции, мы назначали переменную коллекции, а потом делали dump, поскольку коллекция изменялась. В Laravel 5.5 так больше делать не придется, поскольку мы теперь можем вызвать команду dd()
или dump()
прямо из коллекции, что позволяет проводить отладку намного проще.
Предположим, у нас есть коллекция постов, которые подверглись ряду изменений, и нам надо проинспектировать коллекцию на каждом шаге. Тогда делаем следующее:
$posts = Post::all();
$posts
->dump()
->sorBy('title')
->dump()
->pluck('title')
->dump();
На выходе получаем:
Collection {#284 ▼
#items: array:3 [▼
0 => Post {#285 }
1 => Post {#286 }
2 => Post {#287 }
]
}
Collection {#272 ▼
#items: array:3 [▼
0 => Post {#285 }
2 => Post {#287 }
1 => Post {#286 }
]
}
Collection {#268 ▼
#items: array:3 [▼
0 => "Aida Bosco"
1 => "Madge Leuschke"
2 => "Miss Bulah Armstrong Jr."
]
}
Так легче инспектировать содержание коллекции на каждом шаге. Впрочем. Замечу, что между командами dump()
и dd()
есть разница. dump()
выдает результат на данный момент, а затем продолжает работу, тогда как dd()
сразу останавливает процесс и делает dump результатов (dd означает dump and die, сбросить и умереть). Если бы мы вызывали dd()
из коллекции на каждом шаге, то мы бы только получили результат в самой первой точке, когда мы вызвали dd()
из коллекции. Взгляните:
366666
$posts = Post::all();
$posts
->dump()
->sorBy('title')
->dd()
->pluck('title')
->dump();
На выходе получим другое:
Collection {#284 ▼
#items: array:3 [▼
0 => Post {#285 }
1 => Post {#286 }
2 => Post {#287 }
]
}
array:3 [▼
0 => Post {#285 }
2 => Post {#287 }
1 => Post {#286 }
]
Касты в промежуточных таблицах отношений “многие-ко-многим”
Обычно бывает можно объявить свойство casts Модели, которое определяет, как атрибут должен быть сохранен и прочитан. Предположим, у нас есть модель Post, и мы хотим, чтобы одно из полей было сериализовано в JSON при чтении и записи. Следующий сниппет кода нам поможет в этом:
class Post extends Model
{
[...]
protected $casts = [
'somefield' => 'array',
];
[...]
}
Можно было уже кастовать кастомные пивоты в отношения “многие-ко-многим” в версии 5.4, но данные можно было только читать. Если бы мы захотели провести операции записи с данными, нам для начала пришлось бы вручную кастовать значения атрибутов и только потом сохраняться. Теперь так больше делать не надо, поскольку свойство casts
в классах Eloquent\Model
и Eloquent\Relations\Pivot
будет вести себя одинаково, что позволяет использовать методы attach
, sync
и save
в моделях пивотов.
Кастомные директивы Blade::if()
Длинный повторяющийся код проверок в шаблонах blade может сделать их некрасивыми. Хорошая новость в том, что теперь стало возможным извлекать повторяющийся код проверок из шаблонов, что делает их более чистыми и читабельными. Проверки вроде таких:
@if (auth()->check() && auth()->user()->isSubscribed())
<p>Subscribed</p>
@else
<p>Not Subscribed</p>
@endif
Можно заменить на:
@subscribed
<p>Subscribed</p>
@else
<p>Not Subscribed</p>
@endsubscribed
Логика создания кастомной директивы blade добавляется в метод boot
класса AppServiceProvider
:
[...]
use Illuminate\Support\Facades\Blade;
class AppServiceProvider extends ServiceProvider
{
[...]
public function boot()
{
Blade::if('subscribed', function () {
return auth()->check() && auth()->user()->isSubscribed();
});
}
[...]
}
app/Providers/AppServiceProvider.php
Для некоторых проверок может потребоваться передать какой-то метод аргументу. В таком случае мы передаем аргумент в замыкание, когда речь идет о кастомной директиве blade.
@if (auth()->check() && auth()->user()->isFollowing($user->id))
Если использовать такое условие в качестве примера, то видно, что надо передать $user->id
методу isFollowing()
. Чтобы создать кастомную директиву blade, которая принимает $user->id
как аргумент, делаем следующее:
Blade::if('following', function (User $user) {
return auth()->check() && auth()->user()->isFollowing($user->id)
});
Затем, чтобы использовать эту новую директиву в наших шаблонах:
@following($user)
<p>Following</p>
@else
<p>Not Following</p>
@endfollowing
Авторегистрация новых команд artisan в Kernel
Обычно мы создаем новые команды artisan при помощи команды php artisan make:command command-name
. После этого мы назначаем ключ внутри класса с командой, заходим в Kernel и вручную регистрируем команду.
Больше не обязательно регистрировать новые команды в Kernel. У нас теперь есть внутри файла app/Console/kernel.php
новый метод, который отслеживает директорию command и превращает все пути файлов в namespaced пути:
[...]
protected function commands()
{
$this->load(__DIR__.'Commands');
require base_path('routes/console.php');
}
[...]
Предположим, что мы вызвали команду, которая еще не зарегистрирована в ядре. Метод commands() автоматически ее подключит.
Новые методы маршрутов
Это не самая крутая новая функция, но все же стоит упомянуть, что теперь у нас есть два дополнительных метода маршрутов:
Route::view('/welcome', 'welcome');
Route::redirect('home', 'dashboard');
Первый связывает welcome view с путем /welcome, а второй перенаправляет запросы /home
в /dashboard
.
Представляем Laravel Horizon
Это новый пакет Laravel, который предоставляет панель и управляемую кодом систему конфигурации для очередей Laravel Redis:
Horizon в реальном времени показывает нагрузки очередей, недавние задачи, проваленные задачи, попытки перезапуска задач, пропускную способность и рантайм метрики и количество процессов.
Среди функций Horizon есть следующие:
- Высокоуровневая аналитика для задач — такие показатели, как количество задач в минуту и задачи за прошедший час
- Аналитика, специфичная для конкретных задач и очередей.
- Тэги и мониторинг — можно добавлять тэги задачам, а также мониторить конкретные тэги.
- Недавние задачи — можно получить информацию о самых последних задачах.
- Стратегии балансировки очередей — Horizon может автоматически распределять процессы queue worker’а по всем очередям в зависимости от их загрузки.
Taylor Otwell в своей статье подробно рассматривает, как настроить Horizon и все функции, которые в него входят.
Новый трейт миграции баз данных
Это трейт RefreshDatabase
. Кто-то может удивиться, зачем он вообще понадобился, но у причин, которые стоят за ним, есть смысл. Изначально у нас были трейты DatabaseMigrations
и DatabaseTransactions
.
Использование трейта DatabaseMigrations
в тестах помогает убедиться, что миграции проводятся до и после каждого теста, а трейт DatabaseTransactions
позволяет быть уверенным, что база данных восстанавливается в исходное состояние после каждого теста.
Трейт RefreshDatabase
объединяет эти две функции, т.к. единожды мигрирует базу данных в начале тестов, а затем оборачивает каждый тест после этого в транзакции. Преимущество в том, что нам не надо повторно мигрировать базу данных для каждого теста, а это значит, что тесты будут проходить быстрее.
Заключение
Как видите, в новой версии будет много новых крутых фишек. Впрочем, официального релиза еще не было, так что придется подождать, пока опубликуют инструкции для обновления.
Пишите свои мысли и предложения в комментариях и не забудьте поделиться ссылкой на статью с другими разработчиками на Laravel!